Browse Source

Merge d53d91144c into a2c9373ed6

pull/27/merge
Master Kwoth 9 years ago
parent
commit
60c03e5135
100 changed files with 792 additions and 636 deletions
  1. +4
    -4
      docs/features/commands.rst
  2. +75
    -72
      docs/features/events.rst
  3. +29
    -5
      docs/features/logging.rst
  4. +23
    -0
      docs/features/modes.rst
  5. +6
    -6
      docs/features/permissions.rst
  6. +0
    -4
      docs/features/profile.rst
  7. +9
    -5
      docs/getting_started.rst
  8. +13
    -12
      docs/index.rst
  9. +10
    -16
      docs/samples/command.cs
  10. +21
    -20
      docs/samples/command_group.cs
  11. +9
    -0
      docs/samples/command_service_creation.cs
  12. +3
    -3
      docs/samples/getting_started.cs
  13. +8
    -7
      docs/samples/logging.cs
  14. +10
    -24
      docs/samples/permissions.cs
  15. +5
    -5
      src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
  16. +153
    -106
      src/Discord.Net.Audio/AudioClient.cs
  17. +71
    -70
      src/Discord.Net.Audio/AudioService.cs
  18. +4
    -2
      src/Discord.Net.Audio/AudioServiceConfig.cs
  19. +23
    -1
      src/Discord.Net.Audio/IAudioClient.cs
  20. +63
    -37
      src/Discord.Net.Audio/Net/VoiceSocket.cs
  21. +3
    -3
      src/Discord.Net.Audio/Opus/OpusConverter.cs
  22. +0
    -1
      src/Discord.Net.Audio/Opus/OpusDecoder.cs
  23. +1
    -2
      src/Discord.Net.Audio/Opus/OpusEncoder.cs
  24. +0
    -72
      src/Discord.Net.Audio/SimpleAudioClient.cs
  25. +40
    -0
      src/Discord.Net.Audio/VirtualClient.cs
  26. +2
    -2
      src/Discord.Net.Audio/project.json
  27. +2
    -1
      src/Discord.Net.Commands/Command.cs
  28. +17
    -15
      src/Discord.Net.Commands/CommandBuilder.cs
  29. +10
    -6
      src/Discord.Net.Commands/CommandMap.cs
  30. +2
    -2
      src/Discord.Net.Commands/CommandParameter.cs
  31. +3
    -3
      src/Discord.Net.Commands/CommandService.cs
  32. +15
    -14
      src/Discord.Net.Modules/ModuleManager.cs
  33. +63
    -30
      src/Discord.Net.Net45/Discord.Net.csproj
  34. +1
    -1
      src/Discord.Net/API/Client/Common/Channel.cs
  35. +1
    -1
      src/Discord.Net/API/Client/Common/ExtendedGuild.cs
  36. +1
    -1
      src/Discord.Net/API/Client/Common/Guild.cs
  37. +1
    -1
      src/Discord.Net/API/Client/Common/InviteReference.cs
  38. +1
    -1
      src/Discord.Net/API/Client/Common/MemberPresence.cs
  39. +11
    -11
      src/Discord.Net/API/Client/Common/Message.cs
  40. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs
  41. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs
  42. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs
  43. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs
  44. +2
    -2
      src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs
  45. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
  46. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs
  47. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs
  48. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs
  49. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs
  50. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs
  51. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs
  52. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs
  53. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs
  54. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs
  55. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs
  56. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs
  57. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs
  58. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs
  59. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs
  60. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs
  61. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs
  62. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs
  63. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs
  64. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs
  65. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs
  66. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs
  67. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs
  68. +2
    -2
      src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs
  69. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs
  70. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs
  71. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs
  72. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs
  73. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs
  74. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs
  75. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs
  76. +9
    -0
      src/Discord.Net/API/Client/ISerializable.cs
  77. +1
    -1
      src/Discord.Net/API/Client/IWebSocketMessage.cs
  78. +1
    -1
      src/Discord.Net/API/Client/Rest/AcceptInvite.cs
  79. +1
    -1
      src/Discord.Net/API/Client/Rest/AckMessage.cs
  80. +2
    -2
      src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
  81. +1
    -1
      src/Discord.Net/API/Client/Rest/AddGuildBan.cs
  82. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateChannel.cs
  83. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateGuild.cs
  84. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateInvite.cs
  85. +1
    -1
      src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs
  86. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateRole.cs
  87. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteChannel.cs
  88. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteInvite.cs
  89. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteMessage.cs
  90. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteRole.cs
  91. +2
    -2
      src/Discord.Net/API/Client/Rest/Gateway.cs
  92. +1
    -1
      src/Discord.Net/API/Client/Rest/GetBans.cs
  93. +1
    -1
      src/Discord.Net/API/Client/Rest/GetInvite.cs
  94. +1
    -1
      src/Discord.Net/API/Client/Rest/GetInvites.cs
  95. +1
    -1
      src/Discord.Net/API/Client/Rest/GetMessages.cs
  96. +2
    -2
      src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs
  97. +5
    -5
      src/Discord.Net/API/Client/Rest/GetWidget.cs
  98. +1
    -1
      src/Discord.Net/API/Client/Rest/KickMember.cs
  99. +1
    -1
      src/Discord.Net/API/Client/Rest/LeaveGuild.cs
  100. +2
    -2
      src/Discord.Net/API/Client/Rest/Login.cs

+ 4
- 4
docs/features/commands.rst View File

@@ -1,5 +1,5 @@
Commands
========
|stub| Commands
===============


The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands. The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands.


@@ -11,10 +11,10 @@ Example (Simple)
.. literalinclude:: /samples/command.cs .. literalinclude:: /samples/command.cs
:language: csharp6 :language: csharp6
:tab-width: 2 :tab-width: 2
Example (Groups) Example (Groups)
---------------- ----------------


.. literalinclude:: /samples/command_group.cs .. literalinclude:: /samples/command_group.cs
:language: csharp6 :language: csharp6
:tab-width: 2
:tab-width: 2

+ 75
- 72
docs/features/events.rst View File

@@ -1,75 +1,78 @@
|stub| Events
=============
Events
======


Usage Usage
----- -----
To take advantage of Events in Discord.Net, you need to hook into them.

There are two ways of hooking into events. See the example for examples on using these events.

Usable Events
-------------
+--------------------+--------------------+------------------------------------------+
| Event Name | EventArgs | Description |
+====================+====================+==========================================+
| UserBanned | BanEventArgs | Called when a user is banned. |
+--------------------+--------------------+------------------------------------------+
| UserUnbanned | BanEventArgs | Called when a user is unbanned. |
+--------------------+--------------------+------------------------------------------+
| ChannelCreated | ChannelEventArgs | Called when a channel is created. |
+--------------------+--------------------+------------------------------------------+
| ChannelDestroyed | ChannelEventArgs | Called when a channel is destroyed. |
+--------------------+--------------------+------------------------------------------+
| ChannelUpdated | ChannelEventArgs | Called when a channel is updated. |
+--------------------+--------------------+------------------------------------------+
| MessageReceived | MessageEventArgs | Called when a message is received. |
+--------------------+--------------------+------------------------------------------+
| MessageSent | MessageEventArgs | Called when a message is sent. |
+--------------------+--------------------+------------------------------------------+
| MessageDeleted | MessageEventArgs | Called when a message is deleted. |
+--------------------+--------------------+------------------------------------------+
| MessageUpdated | MessageEventArgs | Called when a message is updated\\edited.|
+--------------------+--------------------+------------------------------------------+
| MessageReadRemotely| MessageEventArgs | Called when a message is read. |
+--------------------+--------------------+------------------------------------------+
| RoleCreated | RoleEventArgs | Called when a role is created. |
+--------------------+--------------------+------------------------------------------+
| RoleUpdated | RoleEventArgs | Called when a role is updated. |
+--------------------+--------------------+------------------------------------------+
| RoleDeleted | RoleEventArgs | Called when a role is deleted. |
+--------------------+--------------------+------------------------------------------+
| JoinedServer | ServerEventArgs | Called when a member joins a server. |
+--------------------+--------------------+------------------------------------------+
| LeftServer | ServerEventArgs | Called when a member leaves a server. |
+--------------------+--------------------+------------------------------------------+
| ServerUpdated | ServerEventArgs | Called when a server is updated. |
+--------------------+--------------------+------------------------------------------+
| ServerUnavailable | ServerEventArgs | Called when a Discord server goes down. |
+--------------------+--------------------+------------------------------------------+
| ServerAvailable | ServerEventArgs |Called when a Discord server goes back up.|
+--------------------+--------------------+------------------------------------------+
| UserJoined | UserEventArgs | Called when a user joins a Channel. |
+--------------------+--------------------+------------------------------------------+
| UserLeft | UserEventArgs | Called when a user leaves a Channel. |
+--------------------+--------------------+------------------------------------------+
| UserUpdated | UserEventArgs | --- |
+--------------------+--------------------+------------------------------------------+
| UserPresenceUpdated| UserEventArgs | Called when a user's presence changes. |
| | | (Here\\Away) |
+--------------------+--------------------+------------------------------------------+
| UserVoiceState | UserEventArgs | Called when a user's voice state changes.|
| Updated | | (Muted\\Unmuted) |
+--------------------+--------------------+------------------------------------------+
|UserIsTypingUpdated | UserEventArgs | Called when a user starts\\stops typing. |
+--------------------+--------------------+------------------------------------------+
| UserIsSpeaking | UserEventArgs | Called when a user's voice state changes.|
| Updated | | (Speaking\\Not Speaking) |
+--------------------+--------------------+------------------------------------------+
| ProfileUpdated | N/A | Called when a user's profile changes. |
+--------------------+--------------------+------------------------------------------+
Example
-------
.. literalinclude:: /samples/events.cs
:language: csharp6
:tab-width: 2
Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler<EventArgs> C# pattern.

.. warning::
Note that all synchronous code in an event handler will run on the gateway socket's thread and should be handled as quickly as possible.
Using the async-await pattern to let the thread continue immediately is recommended and is demonstrated in the examples below.

Connection State
----------------

Connection Events will be raised when the Connection State of your client changes.

.. warning::
You should not use DiscordClient.Connected to run code when your client first connects to Discord.
If you lose connection and automatically reconnect, this code will be ran again, which may lead to unexpected behavior.
Messages
--------

- MessageReceived, MessageUpdated and MessageDeleted are raised when a new message arrives, an existing one has been updated (by the user, or by Discord itself), or deleted.
- MessageAcknowledged is only triggered in client mode, and occurs when a message is read on another device logged-in with your account.

Example of MessageReceived:

.. code-block:: c#

// (Preface: Echo Bots are discouraged, make sure your bot is not running in a public server if you use them)

// Hook into the MessageReceived event using a Lambda
_client.MessageReceived += async (s, e) => {
// Check to make sure that the bot is not the author
if (!e.Message.IsAuthor)
// Echo the message back to the channel
await e.Channel.SendMessage(e.Message);
};

Users
-----

There are several user events:

- UserBanned: A user has been banned from a server.
- UserUnbanned: A user was unbanned.
- UserJoined: A user joins a server.
- UserLeft: A user left (or was kicked from) a server.
- UserIsTyping: A user in a channel starts typing.
- UserUpdated: A user object was updated (presence update, role/permission change, or a voice state update).

.. note::
UserUpdated Events include a ``User`` object for Before and After the change.
When accessing the User, you should only use ``e.Before`` if comparing changes, otherwise use ``e.After``

Examples:

.. code-block:: c#

// Register a Hook into the UserBanned event using a Lambda
_client.UserBanned += async (s, e) => {
// Create a Channel object by searching for a channel named '#logs' on the server the ban occurred in.
var logChannel = e.Server.FindChannels("logs").FirstOrDefault();
// Send a message to the server's log channel, stating that a user was banned.
await logChannel.SendMessage($"User Banned: {e.User.Name}");
};

// Register a Hook into the UserUpdated event using a Lambda
_client.UserUpdated += async (s, e) => {
// Check that the user is in a Voice channel
if (e.After.VoiceChannel == null) return;

// See if they changed Voice channels
if (e.Before.VoiceChannel == e.After.VoiceChannel) return;

await logChannel.SendMessage($"User {e.After.Name} changed voice channels!");
};

+ 29
- 5
docs/features/logging.rst View File

@@ -1,11 +1,35 @@
|stub| Logging
==============
Logging
=======


|stub-desc|
Discord.Net will log all of its events/exceptions using a built-in LogManager.
This LogManager can be accessed through DiscordClient.Log

Usage
-----

To handle Log Messages through Discord.Net's Logger, you must hook into the Log.Message<LogMessageEventArgs> Event.

The LogManager does not provide a string-based result for the message, you must put your own message format together using the data provided through LogMessageEventArgs
See the Example for a snippet of logging.

Logging Your Own Data
---------------------

The LogManager included in Discord.Net can also be used to log your own messages.

You can use DiscordClient.Log.Log(LogSeverity, Source, Message, Exception), or one of the shortcut helpers, to log data.

Example:
.. code-block:: c#

_client.MessageReceived += async (s, e) {
// Log a new Message with Severity Info, Sourced from 'MessageReceived', with the Message Contents.
_client.Log.Info("MessageReceived", e.Message.Text, null);
};


Example Example
------- -------


.. literalinclude:: /samples/logging.cs .. literalinclude:: /samples/logging.cs
:language: csharp6
:tab-width: 2
:language: c#
:tab-width: 2

+ 23
- 0
docs/features/modes.rst View File

@@ -0,0 +1,23 @@
Modes
======

Usage
-----
Using this library requires you to state the intention of the program using it.
By default, the library assumes your application is a bot or otherwise automated program, and locks access to certain client-only features.
As we approach the official API, Discord will be creating a divide between bots and clients, so it's important to use the mode appropriate for your program to minimize breaking changes!

.. warning::
This is not a complete list, new features will be added in the future.

Client-Only Features
--------------------

- Message Acknowledgement (Message.Acknowledge(), DiscordClient.MessageAcknowledged)
- Message Importing/Exporting
- Message Read States

Bot-Only Features
-----------------

- Currently, None

+ 6
- 6
docs/features/permissions.rst View File

@@ -1,11 +1,11 @@
Permissions Permissions
==================
===========


There are two types of permissions: *Channel Permissions* and *Server Permissions*. There are two types of permissions: *Channel Permissions* and *Server Permissions*.


Channel Permissions Channel Permissions
------------------- -------------------
Channel Permissions have a set of bools behind them:
Channel Permissions are controlled using a set of flags:


======================= ======= ============== ======================= ======= ==============
Flag Type Description Flag Type Description
@@ -49,7 +49,7 @@ Otherwise, you can use a single DualChannelPermissions.
Server Permissions Server Permissions
------------------ ------------------


Server permisisons are read-only, you cannot change them. You may still access them, however, using User.GetServerPermissions();
Server Permissions can be accessed by ``Server.GetPermissions(User)``, and updated with ``Server.UpdatePermissions(User, ServerPermissions)``


A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions: A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions:


@@ -57,7 +57,7 @@ A user's server permissions also contain the default values for it's channel per
Flag Type Description Flag Type Description
======================= ======= ============== ======================= ======= ==============
BanMembers Server Ban users from the server. BanMembers Server Ban users from the server.
KickMembers Server Kick users from the server. They can stil rejoin.
KickMembers Server Kick users from the server. They can still rejoin.
ManageRoles Server Manage roles on the server, and their permissions. ManageRoles Server Manage roles on the server, and their permissions.
ManageChannels Server Manage channels that exist on the server (add, remove them) ManageChannels Server Manage channels that exist on the server (add, remove them)
ManageServer Server Manage the server settings. ManageServer Server Manage the server settings.
@@ -69,7 +69,7 @@ Managing permissions for roles is much easier than for users in channels. For ro


Example Example
------- -------
.. literalinclude:: /samples/permissions.cs .. literalinclude:: /samples/permissions.cs
:language: csharp6 :language: csharp6
:tab-width: 2
:tab-width: 2

+ 0
- 4
docs/features/profile.rst View File

@@ -1,4 +0,0 @@
|stub| Profile
===================

|stub-desc|

+ 9
- 5
docs/getting_started.rst View File

@@ -4,7 +4,7 @@ Getting Started
Requirements Requirements
------------ ------------


Discord.Net currently requires logging in with a claimed account - anonymous logins are not supported. You can `register for a Discord account here`.
Discord.Net currently requires logging in with a claimed account - anonymous logins are not supported. You can `register for a Discord account here`_.


New accounts are also useless when not connected to a server, so you should create an invite code for whatever server you intend to test on using the official Discord client. New accounts are also useless when not connected to a server, so you should create an invite code for whatever server you intend to test on using the official Discord client.


@@ -17,12 +17,16 @@ You can get Discord.Net from NuGet:


* `Discord.Net`_ * `Discord.Net`_
* `Discord.Net.Commands`_ * `Discord.Net.Commands`_
* `Discord.Net.Modules`_

If you have trouble installing from NuGet, try installing dependencies manually.


You can also pull the latest source from `GitHub`_ You can also pull the latest source from `GitHub`_


.. _Discord.Net: https://discordapp.com/register
.. _Discord.Net.Commands: https://discordapp.com/register
.. _GitHub: https://github.com/RogueException/Discord.Net/>
.. _Discord.Net: https://www.nuget.org/packages/Discord.Net/0.8.1-beta2
.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands/0.8.1-beta2
.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules/0.8.1-beta2
.. _GitHub: https://github.com/RogueException/Discord.Net/


Async Async
----- -----
@@ -39,4 +43,4 @@ Example
.. literalinclude:: samples/getting_started.cs .. literalinclude:: samples/getting_started.cs
:language: csharp6 :language: csharp6
:tab-width: 2
:tab-width: 2

+ 13
- 12
docs/index.rst View File

@@ -1,7 +1,7 @@
Discord.Net Discord.Net
=========== ===========


Discord.Net is an unofficial C# wrapper around the `Discord chat service`.
Discord.Net is an unofficial C# wrapper around the `Discord Chat Service`.
It offers several methods to create automated operations, bots, or even custom clients. It offers several methods to create automated operations, bots, or even custom clients.


Feel free to join us in the `Discord API chat`_. Feel free to join us in the `Discord API chat`_.
@@ -9,28 +9,29 @@ Feel free to join us in the `Discord API chat`_.
.. _Discord chat service: https://discordapp.com .. _Discord chat service: https://discordapp.com
.. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx .. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx


Warning
-------
.. warn::


This is an alpha!
This is a beta!


This library has been built thanks to a community effort reverse engineering the Discord client.
As Discord is still in alpha, it may change at any time without notice, breaking this library as well.
Discord.Net itself is also in early development and you will often encounter breaking changes until the official Discord API is released.
This library has been built thanks to a community effort reverse engineering the Discord client.
As the API is still unofficial, it may change at any time without notice, breaking this library as well.
Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released.


It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_. It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_.


.. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx .. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx


This Documentation is **currently undergoing a rewrite**. Some pages (marked with a wrench) are not updated, or are not completed yet.

.. toctree:: .. toctree::
:caption: Documentation :caption: Documentation
:maxdepth: 2 :maxdepth: 2
getting_started
features/logging

getting_started
features/modes
features/logging
features/management features/management
features/permissions features/permissions
features/profile
features/commands features/commands
features/voice features/voice
features/events
features/events

+ 10
- 16
docs/samples/command.cs View File

@@ -1,16 +1,10 @@
public enum Permissions
{
User,
Moderator,
Admin
}

//Usage: say [text]
client.CreateCommand("say")
.ArgsEqual(1)
.MinPermissions((int)Permissions.User)
.Do(async e =>
{
string msg = Format.Normal(e.CommandText);
await _client.SendMessage(e.Channel, msg);
});
//Since we have setup our CommandChar to be '~', we will run this command by typing ~greet
commands.CreateCommand("greet") //create command greet
.Alias(new string[] { "gr", "hi" }) //add 2 aliases, so it can be run with ~gr and ~hi
.Description("Greets a person.") //add description, it will be shown when ~help is used
.Parameter("GreetedPerson", ParameterType.Required) //as an argument, we have a person we want to greet
.Do(async e =>
{
await client.SendMessage(e.Channel, e.User.Name + " greets " + e.GetArg("GreetedPerson"));
//sends a message to channel with the given text
});

+ 21
- 20
docs/samples/command_group.cs View File

@@ -1,20 +1,21 @@
client.CreateCommandGroup("invites", invites =>
{
invites.DefaultMinPermissions((int)Permissions.Admin);
//Usage: invites accept [inviteCode]
invites.CreateCommand("accept")
.ArgsEqual(1)
.Do(async e =>
{
try
{
await _client.AcceptInvite(e.Args[0]);
await _client.SendMessage(e.Channel, "Invite \"" + e.Args[0] + "\" accepted.");
}
catch (HttpException ex)
{
await _client.SendMessage(e.Channel, "Error: " + ex.Message);
}
});
});
//we would run our commands with ~do greet X and ~do bye X
commands.CreateGroup("do", cgb =>
{
cgb.CreateCommand("greet")
.Alias(new string[] { "gr", "hi" })
.Description("Greets a person.")
.Parameter("GreetedPerson", ParameterType.Required)
.Do(async e =>
{
await client.SendMessage(e.Channel, e.User.Name + " greets " + e.GetArg("GreetedPerson"));
});

cgb.CreateCommand("bye")
.Alias(new string[] { "bb", "gb" })
.Description("Greets a person.")
.Parameter("GreetedPerson", ParameterType.Required)
.Do(async e =>
{
await client.SendMessage(e.Channel, e.User.Name + " says goodbye to " + e.GetArg("GreetedPerson"));
});
});

+ 9
- 0
docs/samples/command_service_creation.cs View File

@@ -0,0 +1,9 @@
//create command service
var commandService = new CommandService(new CommandServiceConfig
{
CommandChar = '~', // prefix char for commands
HelpMode = HelpMode.Public
});

//add command service
var commands = client.AddService(commandService);

+ 3
- 3
docs/samples/getting_started.cs View File

@@ -11,17 +11,17 @@ class Program
client.MessageReceived += async (s, e) => client.MessageReceived += async (s, e) =>
{ {
if (!e.Message.IsAuthor) if (!e.Message.IsAuthor)
await client.SendMessage(e.Channel, e.Message.Text);
await e.Channel.SendMessage(e.Message.Text);
}; };


//Convert our sync method to an async one and block the Main function until the bot disconnects //Convert our sync method to an async one and block the Main function until the bot disconnects
client.Run(async () =>
client.ExecuteAndWait(async () =>
{ {
//Connect to the Discord server using our email and password //Connect to the Discord server using our email and password
await client.Connect("discordtest@email.com", "Password123"); await client.Connect("discordtest@email.com", "Password123");


//If we are not a member of any server, use our invite code (made beforehand in the official Discord Client) //If we are not a member of any server, use our invite code (made beforehand in the official Discord Client)
if (!client.AllServers.Any())
if (!client.Servers.Any())
await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee")); await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee"));
}); });
} }


+ 8
- 7
docs/samples/logging.cs View File

@@ -3,13 +3,14 @@ class Program
private static DiscordBotClient _client; private static DiscordBotClient _client;
static void Main(string[] args) static void Main(string[] args)
{ {
var client = new DiscordClient(new DiscordClientConfig {
//Warning: Debug mode should only be used for identifying problems. It _will_ slow your application down.
LogLevel = LogMessageSeverity.Debug
});
client.LogMessage += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");
client.Run(async () =>
var client = new DiscordClient(x =>
{
LogLevel = LogSeverity.Info
});

_client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");

client.ExecuteAndWait(async () =>
{ {
await client.Connect("discordtest@email.com", "Password123"); await client.Connect("discordtest@email.com", "Password123");
if (!client.Servers.Any()) if (!client.Servers.Any())


+ 10
- 24
docs/samples/permissions.cs View File

@@ -1,28 +1,14 @@
// Finding User Permissions
// Find a User's Channel Permissions
var userChannelPermissions = user.GetPermissions(channel);


void FindPermissions(User u, Channel c)
{
ChannelPermissions cperms = u.GetPermissions(c);
ServerPermissions sperms = u.GetServerPermissions();
}

void SetPermissionsChannelPerms(User u, Channel c)
{
ChannelPermissions allow = new ChannelPermissions();
ChannelPermissions deny = new ChannelPermissions();
// Find a User's Server Permissions
var userServerPermissions = user.ServerPermissions();
var userServerPermissions = server.GetPermissions(user);


allow.Connect = true;
deny.AttachFiles = true;
// Set a User's Channel Permissions (using DualChannelPermissions)


client.SetChannelPermissions(c, u, allow, deny)
var userPerms = user.GetPermissions(channel);
userPerms.ReadMessageHistory = false;
userPerms.AttachFiles = null;
channel.AddPermissionsRule(user, userPerms);
} }

void SetPermissionsDualPerms(User u, Channel c)
{
DualChannelPermissions dual = new DualChannelPermissions();
dual.ReadMessageHistory = false;
dual.Connect = true;
dual.AttachFiles = null;

client.SetChannelPermissions(c, u, dual);
}

+ 5
- 5
src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj View File

@@ -62,8 +62,8 @@
<Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> <Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs">
<Link>InternalIsSpeakingEventArgs.cs</Link> <Link>InternalIsSpeakingEventArgs.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net.Audio\Net\VoiceWebSocket.cs">
<Link>Net\VoiceWebSocket.cs</Link>
<Compile Include="..\Discord.Net.Audio\Net\VoiceSocket.cs">
<Link>Net\VoiceSocket.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> <Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs">
<Link>Opus\OpusConverter.cs</Link> <Link>Opus\OpusConverter.cs</Link>
@@ -74,15 +74,15 @@
<Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs">
<Link>Opus\OpusEncoder.cs</Link> <Link>Opus\OpusEncoder.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs">
<Link>SimpleAudioClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs">
<Link>Sodium\SecretBox.cs</Link> <Link>Sodium\SecretBox.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> <Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs">
<Link>UserIsTalkingEventArgs.cs</Link> <Link>UserIsTalkingEventArgs.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net.Audio\VirtualClient.cs">
<Link>VirtualClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs">
<Link>VoiceBuffer.cs</Link> <Link>VoiceBuffer.cs</Link>
</Compile> </Compile>


+ 153
- 106
src/Discord.Net.Audio/AudioClient.cs View File

@@ -1,9 +1,12 @@
using Discord.API.Client.GatewaySocket; using Discord.API.Client.GatewaySocket;
using Discord.API.Client.Rest;
using Discord.Logging; using Discord.Logging;
using Discord.Net.Rest;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using Nito.AsyncEx; using Nito.AsyncEx;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -41,124 +44,175 @@ namespace Discord.Audio
} }
} }


private readonly DiscordConfig _config;
private readonly AsyncLock _connectionLock; private readonly AsyncLock _connectionLock;
private readonly JsonSerializer _serializer;
private CancellationTokenSource _cancelTokenSource;
private readonly TaskManager _taskManager;
private ConnectionState _gatewayState;


internal AudioService Service { get; }
internal Logger Logger { get; } internal Logger Logger { get; }
public int Id { get; } public int Id { get; }
public AudioService Service { get; }
public AudioServiceConfig Config { get; }
public RestClient ClientAPI { get; }
public GatewaySocket GatewaySocket { get; } public GatewaySocket GatewaySocket { get; }
public VoiceWebSocket VoiceSocket { get; }
public VoiceSocket VoiceSocket { get; }
public JsonSerializer Serializer { get; }
public Stream OutputStream { get; } public Stream OutputStream { get; }

public CancellationToken CancelToken { get; private set; }
public string SessionId { get; private set; }
public ConnectionState State => VoiceSocket.State; public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server; public Server Server => VoiceSocket.Server;
public Channel Channel => VoiceSocket.Channel; public Channel Channel => VoiceSocket.Channel;


public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger)
public AudioClient(DiscordClient client, Server server, int id)
{ {
Service = service;
_serializer = service.Client.Serializer;
Id = clientId;
GatewaySocket = gatewaySocket;
Logger = logger;
OutputStream = new OutStream(this);
Id = id;
_config = client.Config;
Service = client.Audio();
Config = Service.Config;
Serializer = client.Serializer;
_gatewayState = (int)ConnectionState.Disconnected;


_connectionLock = new AsyncLock();
//Logging
Logger = client.Log.CreateLogger($"AudioClient #{id}");


GatewaySocket.ReceivedDispatch += OnReceivedDispatch;
//Async
_taskManager = new TaskManager(Cleanup, false);
_connectionLock = new AsyncLock();
CancelToken = new CancellationToken(true);


VoiceSocket = new VoiceWebSocket(service.Client, this, logger);
//Networking
if (Config.EnableMultiserver)
{
ClientAPI = new JsonRestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}"));
GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}"));
GatewaySocket.Connected += (s, e) =>
{
if (_gatewayState == ConnectionState.Connecting)
EndGatewayConnect();
};
}
else
GatewaySocket = client.GatewaySocket;
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}"));
VoiceSocket.Server = server; VoiceSocket.Server = server;

/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
_voiceSocket.Disconnected += async (s, e) =>
{
_voiceSocket.CurrentServerId;
if (voiceServerId != null)
_gatewaySocket.SendLeaveVoice(voiceServerId.Value);
await _voiceSocket.Disconnect().ConfigureAwait(false);
RaiseVoiceDisconnected(socket.CurrentServerId.Value, e);
if (e.WasUnexpected)
await socket.Reconnect().ConfigureAwait(false);
};*/

/*_voiceSocket.IsSpeaking += (s, e) =>
{
if (_voiceSocket.State == WebSocketState.Connected)
{
var user = _users[e.UserId, socket.CurrentServerId];
bool value = e.IsSpeaking;
if (user.IsSpeaking != value)
{
user.IsSpeaking = value;
var channel = _channels[_voiceSocket.CurrentChannelId];
RaiseUserIsSpeaking(user, channel, value);
if (Config.TrackActivity)
user.UpdateActivity();
}
}
};*/

/*this.Connected += (s, e) =>
{
_voiceSocket.ParentCancelToken = _cancelToken;
};*/
OutputStream = new OutStream(this);
} }


public async Task Join(Channel channel)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (channel.Type != ChannelType.Voice)
throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
if (channel.Server != VoiceSocket.Server)
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
if (channel == VoiceSocket.Channel) return;
if (VoiceSocket.Server == null)
throw new InvalidOperationException("This client has been closed.");
using (await _connectionLock.LockAsync().ConfigureAwait(false))
public async Task Connect()
{
if (Config.EnableMultiserver)
await BeginGatewayConnect().ConfigureAwait(false);
else
{ {
VoiceSocket.Channel = channel;

await Task.Run(() =>
var cancelSource = new CancellationTokenSource();
CancelToken = cancelSource.Token;
await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
}
}
private async Task BeginGatewayConnect()
{
try
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{ {
SendVoiceUpdate();
VoiceSocket.WaitForConnection(_cancelTokenSource.Token);
});
await Disconnect().ConfigureAwait(false);
_taskManager.ClearException();

ClientAPI.Token = Service.Client.ClientAPI.Token;

Stopwatch stopwatch = null;
if (_config.LogLevel >= LogSeverity.Verbose)
stopwatch = Stopwatch.StartNew();
_gatewayState = ConnectionState.Connecting;

var cancelSource = new CancellationTokenSource();
CancelToken = cancelSource.Token;
ClientAPI.CancelToken = CancelToken;
await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false);

await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
GatewaySocket.WaitForConnection(CancelToken);

if (_config.LogLevel >= LogSeverity.Verbose)
{
stopwatch.Stop();
double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2);
Logger.Verbose($"Connection took {seconds} sec");
}
}
}
catch (Exception ex)
{
await _taskManager.SignalError(ex).ConfigureAwait(false);
throw;
} }
} }
private void EndGatewayConnect()
{
_gatewayState = ConnectionState.Connected;
}
public async Task Connect(bool connectGateway)
public async Task Disconnect()
{ {
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
_cancelTokenSource = new CancellationTokenSource();
var cancelToken = _cancelTokenSource.Token;
VoiceSocket.ParentCancelToken = cancelToken;
await _taskManager.Stop(true).ConfigureAwait(false);
if (Config.EnableMultiserver)
ClientAPI.Token = null;
}
private async Task Cleanup()
{
var oldState = _gatewayState;
_gatewayState = ConnectionState.Disconnecting;


if (connectGateway)
if (Config.EnableMultiserver)
{
if (oldState == ConnectionState.Connected)
{ {
GatewaySocket.ParentCancelToken = cancelToken;
await GatewaySocket.Connect().ConfigureAwait(false);
GatewaySocket.WaitForConnection(cancelToken);
try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); }
catch (OperationCanceledException) { }
} }

await GatewaySocket.Disconnect().ConfigureAwait(false);
ClientAPI.Token = null;
} }

var server = VoiceSocket.Server;
VoiceSocket.Server = null;
VoiceSocket.Channel = null;
if (Config.EnableMultiserver)
await Service.RemoveClient(server, this).ConfigureAwait(false);
SendVoiceUpdate(server.Id, null);

await VoiceSocket.Disconnect().ConfigureAwait(false);
if (Config.EnableMultiserver)
await GatewaySocket.Disconnect().ConfigureAwait(false);

_gatewayState = (int)ConnectionState.Disconnected;
} }


public async Task Disconnect()
public async Task Join(Channel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel));
if (channel.Type != ChannelType.Voice)
throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
if (channel == VoiceSocket.Channel) return;
var server = channel.Server;
if (server != VoiceSocket.Server)
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
if (VoiceSocket.Server == null)
throw new InvalidOperationException("This client has been closed.");

SendVoiceUpdate(channel.Server.Id, channel.Id);
using (await _connectionLock.LockAsync().ConfigureAwait(false)) using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false);
VoiceSocket.Channel = null;
SendVoiceUpdate();
await VoiceSocket.Disconnect();
}
await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken));
} }


private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
private async void OnReceivedEvent(WebSocketEventEventArgs e)
{ {
try try
{ {
@@ -166,11 +220,11 @@ namespace Discord.Audio
{ {
case "VOICE_STATE_UPDATE": case "VOICE_STATE_UPDATE":
{ {
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{ {
if (data.ChannelId == null) if (data.ChannelId == null)
await Disconnect();
await Disconnect().ConfigureAwait(false);
else else
{ {
var channel = Service.Client.GetChannel(data.ChannelId.Value); var channel = Service.Client.GetChannel(data.ChannelId.Value);
@@ -179,7 +233,7 @@ namespace Discord.Audio
else else
{ {
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect();
await Disconnect().ConfigureAwait(false);
} }
} }
} }
@@ -187,13 +241,16 @@ namespace Discord.Audio
break; break;
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
{ {
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id) if (data.GuildId == VoiceSocket.Server?.Id)
{ {
var client = Service.Client; var client = Service.Client;
VoiceSocket.Token = data.Token;
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect().ConfigureAwait(false);
var id = client.CurrentUser?.Id;
if (id != null)
{
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false);
}
} }
} }
break; break;
@@ -205,9 +262,6 @@ namespace Discord.Audio
} }
} }


/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
/// <param name="count">Number of bytes in this frame. </param>
public void Send(byte[] data, int offset, int count) public void Send(byte[] data, int offset, int count)
{ {
if (data == null) throw new ArgumentException(nameof(data)); if (data == null) throw new ArgumentException(nameof(data));
@@ -219,29 +273,22 @@ namespace Discord.Audio
VoiceSocket.SendPCMFrames(data, offset, count); VoiceSocket.SendPCMFrames(data, offset, count);
} }


/// <summary> Clears the PCM buffer. </summary>
public void Clear() public void Clear()
{ {
if (VoiceSocket.Server == null) return; //Has been closed if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.ClearPCMFrames(); VoiceSocket.ClearPCMFrames();
} }

/// <summary> Returns a task that completes once the voice output buffer is empty. </summary>
public void Wait() public void Wait()
{ {
if (VoiceSocket.Server == null) return; //Has been closed if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.WaitForQueue(); VoiceSocket.WaitForQueue();
} }


private void SendVoiceUpdate()
public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{ {
var serverId = VoiceSocket.Server?.Id;
if (serverId != null)
{
GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
}
GatewaySocket.SendUpdateVoice(serverId, channelId,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
} }
} }
} }

+ 71
- 70
src/Discord.Net.Audio/AudioService.cs View File

@@ -1,4 +1,4 @@
using Discord.Net.WebSockets;
using Nito.AsyncEx;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; using System.Linq;
@@ -8,8 +8,10 @@ namespace Discord.Audio
{ {
public class AudioService : IService public class AudioService : IService
{ {
private AudioClient _defaultClient;
private ConcurrentDictionary<ulong, IAudioClient> _voiceClients;
private readonly AsyncLock _asyncLock;
private AudioClient _defaultClient; //Only used for single server
private VirtualClient _currentClient; //Only used for single server
private ConcurrentDictionary<ulong, AudioClient> _voiceClients;
private ConcurrentDictionary<User, bool> _talkingUsers; private ConcurrentDictionary<User, bool> _talkingUsers;
private int _nextClientId; private int _nextClientId;


@@ -30,22 +32,24 @@ namespace Discord.Audio
public AudioService(AudioServiceConfig config) public AudioService(AudioServiceConfig config)
{ {
Config = config; Config = config;
}
_asyncLock = new AsyncLock();

}
void IService.Install(DiscordClient client) void IService.Install(DiscordClient client)
{ {
Client = client; Client = client;
Config.Lock(); Config.Lock();


if (Config.EnableMultiserver) if (Config.EnableMultiserver)
_voiceClients = new ConcurrentDictionary<ulong, IAudioClient>();
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>();
else else
{ {
var logger = Client.Log.CreateLogger("Voice"); var logger = Client.Log.CreateLogger("Voice");
_defaultClient = new SimpleAudioClient(this, 0, logger);
_defaultClient = new AudioClient(Client, null, 0);
} }
_talkingUsers = new ConcurrentDictionary<User, bool>(); _talkingUsers = new ConcurrentDictionary<User, bool>();


client.Disconnected += async (s, e) =>
client.GatewaySocket.Disconnected += async (s, e) =>
{ {
if (Config.EnableMultiserver) if (Config.EnableMultiserver)
{ {
@@ -75,68 +79,30 @@ namespace Discord.Audio
{ {
if (server == null) throw new ArgumentNullException(nameof(server)); if (server == null) throw new ArgumentNullException(nameof(server));


if (!Config.EnableMultiserver)
if (Config.EnableMultiserver)
{ {
if (server == _defaultClient.Server)
return (_defaultClient as SimpleAudioClient).CurrentClient;
AudioClient client;
if (_voiceClients.TryGetValue(server.Id, out client))
return client;
else else
return null; return null;
} }
else else
{ {
IAudioClient client;
if (_voiceClients.TryGetValue(server.Id, out client))
return client;
if (server == _currentClient.Server)
return _currentClient;
else else
return null; return null;
} }
} }
private async Task<IAudioClient> CreateClient(Server server)
{
var client = _voiceClients.GetOrAdd(server.Id, _ => null); //Placeholder, so we can't have two clients connecting at once

if (client == null)
{
int id = unchecked(++_nextClientId);

var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}");
var voiceLogger = Client.Log.CreateLogger($"Voice #{id}");
var gatewaySocket = new GatewaySocket(Client, gatewayLogger);
var voiceClient = new AudioClient(this, id, server, Client.GatewaySocket, voiceLogger);

await voiceClient.Connect(true).ConfigureAwait(false);

/*voiceClient.VoiceSocket.FrameReceived += (s, e) =>
{
OnFrameReceieved(e);
};
voiceClient.VoiceSocket.UserIsSpeaking += (s, e) =>
{
var user = server.GetUser(e.UserId);
OnUserIsSpeakingUpdated(user, e.IsSpeaking);
};*/

//Update the placeholder only it still exists (RemoveClient wasnt called)
if (!_voiceClients.TryUpdate(server.Id, voiceClient, null))
{
//If it was, cleanup
await voiceClient.Disconnect().ConfigureAwait(false); ;
await gatewaySocket.Disconnect().ConfigureAwait(false); ;
}
}
return client;
}

//TODO: This isn't threadsafe
internal async Task RemoveClient(Server server, IAudioClient client)
//Called from AudioClient.Disconnect
internal async Task RemoveClient(Server server, AudioClient client)
{ {
if (Config.EnableMultiserver && server != null)
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{ {
if (_voiceClients.TryRemove(server.Id, out client))
{
await client.Disconnect();
await (client as AudioClient).GatewaySocket.Disconnect();
}
if (_voiceClients.TryUpdate(server.Id, null, client))
_voiceClients.TryRemove(server.Id, out client);
} }
} }


@@ -144,16 +110,48 @@ namespace Discord.Audio
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (!Config.EnableMultiserver)
{
await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false);
return _defaultClient;
}
else
var server = channel.Server;
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{ {
var client = await CreateClient(channel.Server).ConfigureAwait(false);
await client.Join(channel).ConfigureAwait(false);
return client;
if (Config.EnableMultiserver)
{
AudioClient client;
if (!_voiceClients.TryGetValue(server.Id, out client))
{
client = new AudioClient(Client, server, unchecked(++_nextClientId));
_voiceClients[server.Id] = client;

await client.Connect().ConfigureAwait(false);

/*voiceClient.VoiceSocket.FrameReceived += (s, e) =>
{
OnFrameReceieved(e);
};
voiceClient.VoiceSocket.UserIsSpeaking += (s, e) =>
{
var user = server.GetUser(e.UserId);
OnUserIsSpeakingUpdated(user, e.IsSpeaking);
};*/
}

await client.Join(channel).ConfigureAwait(false);
return client;
}
else
{
if (_defaultClient.Server != server)
{
await _defaultClient.Disconnect().ConfigureAwait(false);
_defaultClient.VoiceSocket.Server = server;
await _defaultClient.Connect().ConfigureAwait(false);
}
var client = new VirtualClient(_defaultClient, server);
_currentClient = client;

await client.Join(channel).ConfigureAwait(false);
return client;
}

} }
} }
@@ -163,15 +161,18 @@ namespace Discord.Audio


if (Config.EnableMultiserver) if (Config.EnableMultiserver)
{ {
IAudioClient client;
AudioClient client;
if (_voiceClients.TryRemove(server.Id, out client)) if (_voiceClients.TryRemove(server.Id, out client))
await client.Disconnect().ConfigureAwait(false); await client.Disconnect().ConfigureAwait(false);
} }
else else
{ {
IAudioClient client = GetClient(server);
if (client != null)
await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false);
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
var client = GetClient(server) as VirtualClient;
if (client != null)
await _defaultClient.Disconnect().ConfigureAwait(false);
}
} }
} }
} }


+ 4
- 2
src/Discord.Net.Audio/AudioServiceConfig.cs View File

@@ -12,6 +12,8 @@ namespace Discord.Audio


public class AudioServiceConfig public class AudioServiceConfig
{ {
public const int MaxBitrate = 128;

/// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary> /// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary>
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } }
private int _connectionTimeout = 30000; private int _connectionTimeout = 30000;
@@ -33,8 +35,8 @@ namespace Discord.Audio
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } }
private int _bufferLength = 1000; private int _bufferLength = 1000;


/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and 512 inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary>
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary>
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
private int? _bitrate = null; private int? _bitrate = null;
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary> /// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary>
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } } public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } }


+ 23
- 1
src/Discord.Net.Audio/IAudioClient.cs View File

@@ -1,16 +1,38 @@
using System.IO;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Audio namespace Discord.Audio
{ {
public interface IAudioClient public interface IAudioClient
{ {
/// <summary> Gets the unique identifier for this client. </summary>
int Id { get; }
/// <summary> Gets the session id for the current connection. </summary>
string SessionId { get; }
/// <summary> Gets the current state of this client. </summary>
ConnectionState State { get; } ConnectionState State { get; }
/// <summary> Gets the channel this client is currently a member of. </summary>
Channel Channel { get; } Channel Channel { get; }
/// <summary> Gets the server this client is bound to. </summary>
Server Server { get; } Server Server { get; }
/// <summary> Gets a stream object that wraps the Send() function. </summary>
Stream OutputStream { get; } Stream OutputStream { get; }
/// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary>
CancellationToken CancelToken { get; }


/// <summary> Gets the internal RestClient for the Client API endpoint. </summary>
RestClient ClientAPI { get; }
/// <summary> Gets the internal WebSocket for the Gateway event stream. </summary>
GatewaySocket GatewaySocket { get; }
/// <summary> Gets the internal WebSocket for the Voice control stream. </summary>
VoiceSocket VoiceSocket { get; }

/// <summary> Moves the client to another channel on the same server. </summary>
Task Join(Channel channel); Task Join(Channel channel);
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
Task Disconnect(); Task Disconnect();


/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> /// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>


src/Discord.Net.Audio/Net/VoiceWebSocket.cs → src/Discord.Net.Audio/Net/VoiceSocket.cs View File

@@ -10,6 +10,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@@ -19,7 +20,7 @@ using System.Threading.Tasks;


namespace Discord.Net.WebSockets namespace Discord.Net.WebSockets
{ {
public partial class VoiceWebSocket : WebSocket
public partial class VoiceSocket : WebSocket
{ {
private const int MaxOpusSize = 4000; private const int MaxOpusSize = 4000;
private const string EncryptedMode = "xsalsa20_poly1305"; private const string EncryptedMode = "xsalsa20_poly1305";
@@ -27,8 +28,7 @@ namespace Discord.Net.WebSockets


private readonly int _targetAudioBufferLength; private readonly int _targetAudioBufferLength;
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders;
private readonly AudioClient _audioClient;
private readonly AudioServiceConfig _config;
private readonly AudioServiceConfig _audioConfig;
private Task _sendTask, _receiveTask; private Task _sendTask, _receiveTask;
private VoiceBuffer _sendBuffer; private VoiceBuffer _sendBuffer;
private OpusEncoder _encoder; private OpusEncoder _encoder;
@@ -41,6 +41,8 @@ namespace Discord.Net.WebSockets
private ushort _sequence; private ushort _sequence;
private string _encryptionMode; private string _encryptionMode;
private int _ping; private int _ping;
private ulong? _userId;
private string _sessionId;


public string Token { get; internal set; } public string Token { get; internal set; }
public Server Server { get; internal set; } public Server Server { get; internal set; }
@@ -57,32 +59,37 @@ namespace Discord.Net.WebSockets
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count)
=> FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count));


internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, Logger logger)
: base(client, logger)
internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger)
: base(config, serializer, logger)
{ {
_audioClient = audioClient;
_config = client.Audio().Config;
_audioConfig = audioConfig;
_decoders = new ConcurrentDictionary<uint, OpusDecoder>(); _decoders = new ConcurrentDictionary<uint, OpusDecoder>();
_targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize]; _encodingBuffer = new byte[MaxOpusSize];
_ssrcMapping = new ConcurrentDictionary<uint, ulong>(); _ssrcMapping = new ConcurrentDictionary<uint, ulong>();
_encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed);
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
_encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed);
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
} }


public Task Connect()
=> BeginConnect();
public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken)
{
Host = host;
Token = token;
_userId = userId;
_sessionId = sessionId;
return BeginConnect(parentCancelToken);
}
private async Task Reconnect() private async Task Reconnect()
{ {
try try
{ {
var cancelToken = ParentCancelToken.Value;
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
var cancelToken = _parentCancelToken;
await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
try try
{ {
await Connect().ConfigureAwait(false);
await BeginConnect(_parentCancelToken).ConfigureAwait(false);
break; break;
} }
catch (OperationCanceledException) { throw; } catch (OperationCanceledException) { throw; }
@@ -90,40 +97,52 @@ namespace Discord.Net.WebSockets
{ {
Logger.Error("Reconnect failed", ex); Logger.Error("Reconnect failed", ex);
//Net is down? We can keep trying to reconnect until the user runs Disconnect() //Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
} }
} }
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }
} }
public Task Disconnect() => _taskManager.Stop(true);
public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
_userId = null;
}


protected override async Task Run() protected override async Task Run()
{ {
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));


List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
if (_config.Mode.HasFlag(AudioMode.Outgoing))
if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing))
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); _sendTask = Task.Run(() => SendVoiceAsync(CancelToken));
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); _receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken));


SendIdentify();
SendIdentify(_userId.Value, _sessionId);


#if !DOTNET5_4 #if !DOTNET5_4
tasks.Add(WatcherAsync()); tasks.Add(WatcherAsync());
#endif #endif
tasks.AddRange(_engine.GetTasks(CancelToken)); tasks.AddRange(_engine.GetTasks(CancelToken));
tasks.Add(HeartbeatAsync(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken));
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false);
} }
protected override async Task Cleanup() protected override async Task Cleanup()
{ {
var sendThread = _sendTask; var sendThread = _sendTask;
if (sendThread != null) await sendThread;
if (sendThread != null)
{
try { await sendThread.ConfigureAwait(false); }
catch (Exception) { } //Ignore any errors during cleanup
}
_sendTask = null; _sendTask = null;


var receiveThread = _receiveTask; var receiveThread = _receiveTask;
if (receiveThread != null) await receiveThread;
if (receiveThread != null)
{
try { await receiveThread.ConfigureAwait(false); }
catch (Exception) { } //Ignore any errors during cleanup
}
_receiveTask = null; _receiveTask = null;


OpusDecoder decoder; OpusDecoder decoder;
@@ -136,7 +155,7 @@ namespace Discord.Net.WebSockets
ClearPCMFrames(); ClearPCMFrames();
_udp = null; _udp = null;


await base.Cleanup();
await base.Cleanup().ConfigureAwait(false);
} }


private async Task ReceiveVoiceAsync(CancellationToken cancelToken) private async Task ReceiveVoiceAsync(CancellationToken cancelToken)
@@ -148,7 +167,7 @@ namespace Discord.Net.WebSockets
int packetLength, resultOffset, resultLength; int packetLength, resultOffset, resultLength;
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);


if ((_config.Mode & AudioMode.Incoming) != 0)
if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
{ {
decodingBuffer = new byte[MaxOpusSize]; decodingBuffer = new byte[MaxOpusSize];
nonce = new byte[24]; nonce = new byte[24];
@@ -156,7 +175,7 @@ namespace Discord.Net.WebSockets


while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
await Task.Delay(1);
await Task.Delay(1).ConfigureAwait(false);
if (_udp.Available > 0) if (_udp.Available > 0)
{ {
#if !DOTNET5_4 #if !DOTNET5_4
@@ -184,7 +203,7 @@ namespace Discord.Net.WebSockets
int port = packet[68] | packet[69] << 8; int port = packet[68] | packet[69] << 8;


SendSelectProtocol(ip, port); SendSelectProtocol(ip, port);
if ((_config.Mode & AudioMode.Incoming) == 0)
if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
return; //We dont need this thread anymore return; //We dont need this thread anymore
} }
else else
@@ -246,7 +265,7 @@ namespace Discord.Net.WebSockets
try try
{ {
while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected)
await Task.Delay(1);
await Task.Delay(1).ConfigureAwait(false);


if (cancelToken.IsCancellationRequested) if (cancelToken.IsCancellationRequested)
return; return;
@@ -362,10 +381,10 @@ namespace Discord.Net.WebSockets
{ {
int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond);
if (time > 0) if (time > 0)
await Task.Delay(time);
await Task.Delay(time).ConfigureAwait(false);
} }
else else
await Task.Delay(1); //Give as much time to the encrypter as possible
await Task.Delay(1).ConfigureAwait(false); //Give as much time to the encrypter as possible
} }
} }
} }
@@ -374,14 +393,21 @@ namespace Discord.Net.WebSockets
} }
#if !DOTNET5_4 #if !DOTNET5_4
//Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken
private Task WatcherAsync()
=> CancelToken.Wait().ContinueWith(_ => _udp.Close());
private async Task WatcherAsync()
{
await CancelToken.Wait();
_udp.Close();
}
#endif #endif


protected override async Task ProcessMessage(string json) protected override async Task ProcessMessage(string json)
{ {
await base.ProcessMessage(json).ConfigureAwait(false); await base.ProcessMessage(json).ConfigureAwait(false);
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);

WebSocketMessage msg;
using (var reader = new JsonTextReader(new StringReader(json)))
msg = _serializer.Deserialize(reader, typeof(WebSocketMessage)) as WebSocketMessage;
var opCode = (OpCodes)msg.Operation; var opCode = (OpCodes)msg.Operation;
switch (opCode) switch (opCode)
{ {
@@ -395,7 +421,7 @@ namespace Discord.Net.WebSockets
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
_endpoint = new IPEndPoint(address, payload.Port); _endpoint = new IPEndPoint(address, payload.Port);


if (_config.EnableEncryption)
if (_audioConfig.EnableEncryption)
{ {
if (payload.Modes.Contains(EncryptedMode)) if (payload.Modes.Contains(EncryptedMode))
{ {
@@ -436,7 +462,7 @@ namespace Discord.Net.WebSockets
var payload = (msg.Payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); var payload = (msg.Payload as JToken).ToObject<SessionDescriptionEvent>(_serializer);
_secretKey = payload.SecretKey; _secretKey = payload.SecretKey;
SendSetSpeaking(true); SendSetSpeaking(true);
await EndConnect();
await EndConnect().ConfigureAwait(false);
} }
break; break;
case OpCodes.Speaking: case OpCodes.Speaking:
@@ -467,12 +493,12 @@ namespace Discord.Net.WebSockets


public override void SendHeartbeat() public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand()); => QueueMessage(new HeartbeatCommand());
public void SendIdentify()
public void SendIdentify(ulong id, string sessionId)
=> QueueMessage(new IdentifyCommand => QueueMessage(new IdentifyCommand
{ {
GuildId = Server.Id, GuildId = Server.Id,
UserId = _client.CurrentUser.Id,
SessionId = _client.SessionId,
UserId = id,
SessionId = sessionId,
Token = Token Token = Token
}); });
public void SendSelectProtocol(string externalAddress, int externalPort) public void SendSelectProtocol(string externalAddress, int externalPort)

+ 3
- 3
src/Discord.Net.Audio/Opus/OpusConverter.cs View File

@@ -4,13 +4,13 @@ using System.Security;


namespace Discord.Audio.Opus namespace Discord.Audio.Opus
{ {
public enum OpusApplication : int
internal enum OpusApplication : int
{ {
Voice = 2048, Voice = 2048,
MusicOrMixed = 2049, MusicOrMixed = 2049,
LowLatency = 2051 LowLatency = 2051
} }
public enum OpusError : int
internal enum OpusError : int
{ {
OK = 0, OK = 0,
BadArg = -1, BadArg = -1,
@@ -22,7 +22,7 @@ namespace Discord.Audio.Opus
AllocFail = -7 AllocFail = -7
} }


public abstract class OpusConverter : IDisposable
internal abstract class OpusConverter : IDisposable
{ {
protected enum Ctl : int protected enum Ctl : int
{ {


+ 0
- 1
src/Discord.Net.Audio/Opus/OpusDecoder.cs View File

@@ -2,7 +2,6 @@


namespace Discord.Audio.Opus namespace Discord.Audio.Opus
{ {
/// <summary> Opus codec wrapper. </summary>
internal class OpusDecoder : OpusConverter internal class OpusDecoder : OpusConverter
{ {
/// <summary> Creates a new Opus decoder. </summary> /// <summary> Creates a new Opus decoder. </summary>


+ 1
- 2
src/Discord.Net.Audio/Opus/OpusEncoder.cs View File

@@ -2,7 +2,6 @@


namespace Discord.Audio.Opus namespace Discord.Audio.Opus
{ {
/// <summary> Opus codec wrapper. </summary>
internal class OpusEncoder : OpusConverter internal class OpusEncoder : OpusConverter
{ {
/// <summary> Gets the bit rate in kbit/s. </summary> /// <summary> Gets the bit rate in kbit/s. </summary>
@@ -19,7 +18,7 @@ namespace Discord.Audio.Opus
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application)
: base(samplingRate, channels, frameLength) : base(samplingRate, channels, frameLength)
{ {
if (bitrate != null && (bitrate < 1 || bitrate > 512))
if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate))
throw new ArgumentOutOfRangeException(nameof(bitrate)); throw new ArgumentOutOfRangeException(nameof(bitrate));


BitRate = bitrate; BitRate = bitrate;


+ 0
- 72
src/Discord.Net.Audio/SimpleAudioClient.cs View File

@@ -1,72 +0,0 @@
using Discord.Logging;
using Nito.AsyncEx;
using System.IO;
using System.Threading.Tasks;

namespace Discord.Audio
{
internal class SimpleAudioClient : AudioClient
{
internal class VirtualClient : IAudioClient
{
private readonly SimpleAudioClient _client;

ConnectionState IAudioClient.State => _client.VoiceSocket.State;
Server IAudioClient.Server => _client.VoiceSocket.Server;
Channel IAudioClient.Channel => _client.VoiceSocket.Channel;
Stream IAudioClient.OutputStream => _client.OutputStream;

public VirtualClient(SimpleAudioClient client)
{
_client = client;
}

Task IAudioClient.Disconnect() => _client.Leave(this);
Task IAudioClient.Join(Channel channel) => _client.Join(channel);

void IAudioClient.Send(byte[] data, int offset, int count) => _client.Send(data, offset, count);
void IAudioClient.Clear() => _client.Clear();
void IAudioClient.Wait() => _client.Wait();
}

private readonly AsyncLock _connectionLock;

internal VirtualClient CurrentClient { get; private set; }

public SimpleAudioClient(AudioService service, int id, Logger logger)
: base(service, id, null, service.Client.GatewaySocket, logger)
{
_connectionLock = new AsyncLock();
}

//Only disconnects if is current a member of this server
public async Task Leave(VirtualClient client)
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
if (CurrentClient == client)
{
CurrentClient = null;
await Disconnect().ConfigureAwait(false);
}
}
}

internal async Task<IAudioClient> Connect(Channel channel, bool connectGateway)
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
bool changeServer = channel.Server != VoiceSocket.Server;
if (changeServer || CurrentClient == null)
{
await Disconnect().ConfigureAwait(false);
CurrentClient = new VirtualClient(this);
VoiceSocket.Server = channel.Server;
await Connect(connectGateway).ConfigureAwait(false);
}
await Join(channel).ConfigureAwait(false);
return CurrentClient;
}
}
}
}

+ 40
- 0
src/Discord.Net.Audio/VirtualClient.cs View File

@@ -0,0 +1,40 @@
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
internal class VirtualClient : IAudioClient
{
private readonly AudioClient _client;

public Server Server { get; }

public int Id => 0;
public string SessionId => _client.Server == Server ? _client.SessionId : null;

public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected;
public Channel Channel => _client.Server == Server ? _client.Channel : null;
public Stream OutputStream => _client.Server == Server ? _client.OutputStream : null;
public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None;

public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null;
public GatewaySocket GatewaySocket => _client.Server == Server ? _client.GatewaySocket : null;
public VoiceSocket VoiceSocket => _client.Server == Server ? _client.VoiceSocket : null;

public VirtualClient(AudioClient client, Server server)
{
_client = client;
Server = server;
}

public Task Disconnect() => _client.Service.Leave(Server);
public Task Join(Channel channel) => _client.Join(channel);

public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count);
public void Clear() => _client.Clear();
public void Wait() => _client.Wait();
}
}

+ 2
- 2
src/Discord.Net.Audio/project.json View File

@@ -13,8 +13,8 @@
"contentFiles": [ "libsodium.dll", "opus.dll" ], "contentFiles": [ "libsodium.dll", "opus.dll" ],


"compilationOptions": { "compilationOptions": {
"warningsAsErrors": true,
"allowUnsafe": true
"allowUnsafe": true,
"warningsAsErrors": true
}, },


"dependencies": { "dependencies": {


+ 2
- 1
src/Discord.Net.Commands/Command.cs View File

@@ -5,7 +5,8 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
public sealed class Command
//TODO: Make this more friendly and expose it to be extendable
public class Command
{ {
private string[] _aliases; private string[] _aliases;
internal CommandParameter[] _parameters; internal CommandParameter[] _parameters;


+ 17
- 15
src/Discord.Net.Commands/CommandBuilder.cs View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;


namespace Discord.Commands namespace Discord.Commands
{ {
//TODO: Make this more friendly and expose it to be extendable
public sealed class CommandBuilder public sealed class CommandBuilder
{ {
private readonly CommandService _service; private readonly CommandService _service;
@@ -18,17 +19,20 @@ namespace Discord.Commands


public CommandService Service => _service; public CommandService Service => _service;


internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null)
internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null)
{ {
_service = service;
_command = command;
_command.Category = category;
_params = new List<CommandParameter>();
_service = service;
_prefix = prefix;

_command = new Command(AppendPrefix(prefix, text));
_command.Category = category;

if (initialChecks != null) if (initialChecks != null)
_checks = new List<IPermissionChecker>(initialChecks); _checks = new List<IPermissionChecker>(initialChecks);
else else
_checks = new List<IPermissionChecker>(); _checks = new List<IPermissionChecker>();
_prefix = prefix;

_params = new List<CommandParameter>();
_aliases = new List<string>(); _aliases = new List<string>();


_allowRequiredParams = true; _allowRequiredParams = true;
@@ -112,7 +116,7 @@ namespace Discord.Commands
return prefix; return prefix;
} }
} }
public sealed class CommandGroupBuilder
public class CommandGroupBuilder
{ {
private readonly CommandService _service; private readonly CommandService _service;
private readonly string _prefix; private readonly string _prefix;
@@ -121,10 +125,11 @@ namespace Discord.Commands


public CommandService Service => _service; public CommandService Service => _service;


internal CommandGroupBuilder(CommandService service, string prefix, IEnumerable<IPermissionChecker> initialChecks = null)
internal CommandGroupBuilder(CommandService service, string prefix = "", string category = null, IEnumerable<IPermissionChecker> initialChecks = null)
{ {
_service = service; _service = service;
_prefix = prefix; _prefix = prefix;
_category = category;
if (initialChecks != null) if (initialChecks != null)
_checks = new List<IPermissionChecker>(initialChecks); _checks = new List<IPermissionChecker>(initialChecks);
else else
@@ -145,17 +150,14 @@ namespace Discord.Commands
_checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg));
} }


public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config = null)
{
config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _checks));
public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config)
{
config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _category, _checks));
return this; return this;
} }
public CommandBuilder CreateCommand() public CommandBuilder CreateCommand()
=> CreateCommand(""); => CreateCommand("");
public CommandBuilder CreateCommand(string cmd) public CommandBuilder CreateCommand(string cmd)
{
var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd));
return new CommandBuilder(_service, command, _prefix, _category, _checks);
}
=> new CommandBuilder(_service, cmd, _prefix, _category, _checks);
} }
} }

+ 10
- 6
src/Discord.Net.Commands/CommandMap.cs View File

@@ -20,16 +20,20 @@ namespace Discord.Commands
public IEnumerable<Command> Commands => _commands; public IEnumerable<Command> Commands => _commands;
public IEnumerable<CommandMap> SubGroups => _items.Values; public IEnumerable<CommandMap> SubGroups => _items.Values;


public CommandMap(CommandMap parent, string name, string fullName)
public CommandMap()
{
_items = new Dictionary<string, CommandMap>();
_commands = new List<Command>();
_isVisible = false;
_hasNonAliases = false;
_hasSubGroups = false;
}
public CommandMap(CommandMap parent, string name, string fullName)
: this()
{ {
_parent = parent; _parent = parent;
_name = name; _name = name;
_fullName = fullName; _fullName = fullName;
_items = new Dictionary<string, CommandMap>();
_commands = new List<Command>();
_isVisible = false;
_hasNonAliases = false;
_hasSubGroups = false;
} }
public CommandMap GetItem(string text) public CommandMap GetItem(string text)


+ 2
- 2
src/Discord.Net.Commands/CommandParameter.cs View File

@@ -11,13 +11,13 @@
/// <summary> Catches all remaining text as a single optional parameter. </summary> /// <summary> Catches all remaining text as a single optional parameter. </summary>
Unparsed Unparsed
} }
public sealed class CommandParameter
public class CommandParameter
{ {
public string Name { get; } public string Name { get; }
public int Id { get; internal set; } public int Id { get; internal set; }
public ParameterType Type { get; } public ParameterType Type { get; }


public CommandParameter(string name, ParameterType type)
internal CommandParameter(string name, ParameterType type)
{ {
Name = name; Name = name;
Type = type; Type = type;


+ 3
- 3
src/Discord.Net.Commands/CommandService.cs View File

@@ -34,9 +34,9 @@ namespace Discord.Commands
Config = config; Config = config;


_allCommands = new List<Command>(); _allCommands = new List<Command>();
_map = new CommandMap(null, "", "");
_map = new CommandMap();
_categories = new Dictionary<string, CommandMap>(); _categories = new Dictionary<string, CommandMap>();
Root = new CommandGroupBuilder(this, "", null);
Root = new CommandGroupBuilder(this);
} }


void IService.Install(DiscordClient client) void IService.Install(DiscordClient client)
@@ -309,7 +309,7 @@ namespace Discord.Commands
string categoryName = command.Category ?? ""; string categoryName = command.Category ?? "";
if (!_categories.TryGetValue(categoryName, out category)) if (!_categories.TryGetValue(categoryName, out category))
{ {
category = new CommandMap(null, "", "");
category = new CommandMap();
_categories.Add(categoryName, category); _categories.Add(categoryName, category);
} }




+ 15
- 14
src/Discord.Net.Modules/ModuleManager.cs View File

@@ -7,7 +7,7 @@ using System.Linq;


namespace Discord.Modules namespace Discord.Modules
{ {
public sealed class ModuleManager
public class ModuleManager
{ {
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; public event EventHandler<ServerEventArgs> ServerEnabled = delegate { };
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; public event EventHandler<ServerEventArgs> ServerDisabled = delegate { };
@@ -15,31 +15,31 @@ namespace Discord.Modules
public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { }; public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { };


public event EventHandler<ServerEventArgs> LeftServer = delegate { }; public event EventHandler<ServerEventArgs> LeftServer = delegate { };
public event EventHandler<ServerEventArgs> ServerUpdated = delegate { };
public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { };
public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { };
public event EventHandler<ServerEventArgs> ServerAvailable = delegate { }; public event EventHandler<ServerEventArgs> ServerAvailable = delegate { };
public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { }; public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { };
public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { }; public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { };
public event EventHandler<ChannelEventArgs> ChannelUpdated = delegate { };
public event EventHandler<ChannelUpdatedEventArgs> ChannelUpdated = delegate { };


public event EventHandler<RoleEventArgs> RoleCreated = delegate { }; public event EventHandler<RoleEventArgs> RoleCreated = delegate { };
public event EventHandler<RoleEventArgs> RoleUpdated = delegate { };
public event EventHandler<RoleUpdatedEventArgs> RoleUpdated = delegate { };
public event EventHandler<RoleEventArgs> RoleDeleted = delegate { }; public event EventHandler<RoleEventArgs> RoleDeleted = delegate { };


public event EventHandler<UserEventArgs> UserBanned = delegate { }; public event EventHandler<UserEventArgs> UserBanned = delegate { };
public event EventHandler<UserEventArgs> UserJoined = delegate { }; public event EventHandler<UserEventArgs> UserJoined = delegate { };
public event EventHandler<UserEventArgs> UserLeft = delegate { }; public event EventHandler<UserEventArgs> UserLeft = delegate { };
public event EventHandler<UserEventArgs> UserUpdated = delegate { };
public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { };
public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { };
public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { };
//public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { };
//public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { };
public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; public event EventHandler<UserEventArgs> UserUnbanned = delegate { };
public event EventHandler<ChannelUserEventArgs> UserIsTypingUpdated = delegate { };
public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { };


public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
public event EventHandler<MessageEventArgs> MessageSent = delegate { }; public event EventHandler<MessageEventArgs> MessageSent = delegate { };
public event EventHandler<MessageEventArgs> MessageDeleted = delegate { }; public event EventHandler<MessageEventArgs> MessageDeleted = delegate { };
public event EventHandler<MessageEventArgs> MessageUpdated = delegate { };
public event EventHandler<MessageUpdatedEventArgs> MessageUpdated = delegate { };
public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { }; public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { };
private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate;
@@ -79,11 +79,12 @@ namespace Discord.Modules
if (_allowAll || _useServerWhitelist) //Server-only events if (_allowAll || _useServerWhitelist) //Server-only events
{ {
client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); };
client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); };
//TODO: This *is* a channel update if the before/after voice channel is whitelisted
//client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); };
} }


client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); }; client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); };
client.ChannelUpdated += (s, e) => { if (HasChannel(e.Channel)) ChannelUpdated(s, e); };
client.ChannelUpdated += (s, e) => { if (HasChannel(e.After)) ChannelUpdated(s, e); };


client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); }; client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); };
client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); }; client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); };
@@ -96,16 +97,16 @@ namespace Discord.Modules
client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); }; client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); };


client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } }; client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } };
client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.Server)) ServerUpdated(s, e); };
client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.After)) ServerUpdated(s, e); };
client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); }; client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); };
client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); }; client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); };


client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); };
client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); };
client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); };
client.UserIsTypingUpdated += (s, e) => { if (HasChannel(e.Channel)) UserIsTypingUpdated(s, e); };
client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); };
//TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist
client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); };
//client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); };
client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); };
client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); };
} }


+ 63
- 30
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -385,18 +385,9 @@
<Compile Include="..\Discord.Net\API\Status\Rest\UpcomingMaintenances.cs"> <Compile Include="..\Discord.Net\API\Status\Rest\UpcomingMaintenances.cs">
<Link>API\Status\Rest\UpcomingMaintenances.cs</Link> <Link>API\Status\Rest\UpcomingMaintenances.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\ChannelEventArgs.cs">
<Link>ChannelEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\ChannelUserEventArgs.cs">
<Link>ChannelUserEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Config.cs"> <Compile Include="..\Discord.Net\Config.cs">
<Link>Config.cs</Link> <Link>Config.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\DisconnectedEventArgs.cs">
<Link>DisconnectedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordClient.cs"> <Compile Include="..\Discord.Net\DiscordClient.cs">
<Link>DiscordClient.cs</Link> <Link>DiscordClient.cs</Link>
</Compile> </Compile>
@@ -406,6 +397,9 @@
<Compile Include="..\Discord.Net\DiscordConfig.cs"> <Compile Include="..\Discord.Net\DiscordConfig.cs">
<Link>DiscordConfig.cs</Link> <Link>DiscordConfig.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\DynamicIL.cs">
<Link>DynamicIL.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\ChannelType.cs"> <Compile Include="..\Discord.Net\Enums\ChannelType.cs">
<Link>Enums\ChannelType.cs</Link> <Link>Enums\ChannelType.cs</Link>
</Compile> </Compile>
@@ -418,12 +412,66 @@
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> <Compile Include="..\Discord.Net\Enums\PermissionTarget.cs">
<Link>Enums\PermissionTarget.cs</Link> <Link>Enums\PermissionTarget.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Enums\Relative.cs">
<Link>Enums\Relative.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\StringEnum.cs"> <Compile Include="..\Discord.Net\Enums\StringEnum.cs">
<Link>Enums\StringEnum.cs</Link> <Link>Enums\StringEnum.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Enums\UserStatus.cs"> <Compile Include="..\Discord.Net\Enums\UserStatus.cs">
<Link>Enums\UserStatus.cs</Link> <Link>Enums\UserStatus.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\ETF\ETFReader.cs">
<Link>ETF\ETFReader.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\ETF\ETFType.cs">
<Link>ETF\ETFType.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\ETF\ETFWriter.cs">
<Link>ETF\ETFWriter.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ChannelEventArgs.cs">
<Link>Events\ChannelEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ChannelUpdatedEventArgs.cs">
<Link>Events\ChannelUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ChannelUserEventArgs.cs">
<Link>Events\ChannelUserEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\DisconnectedEventArgs.cs">
<Link>Events\DisconnectedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\LogMessageEventArgs.cs">
<Link>Events\LogMessageEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\MessageEventArgs.cs">
<Link>Events\MessageEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\MessageUpdatedEventArgs.cs">
<Link>Events\MessageUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ProfileUpdatedEventArgs.cs">
<Link>Events\ProfileUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\RoleEventArgs.cs">
<Link>Events\RoleEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\RoleUpdatedEventArgs.cs">
<Link>Events\RoleUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ServerEventArgs.cs">
<Link>Events\ServerEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\ServerUpdatedEventArgs.cs">
<Link>Events\ServerUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\UserEventArgs.cs">
<Link>Events\UserEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Events\UserUpdatedEventArgs.cs">
<Link>Events\UserUpdatedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Extensions.cs"> <Compile Include="..\Discord.Net\Extensions.cs">
<Link>Extensions.cs</Link> <Link>Extensions.cs</Link>
</Compile> </Compile>
@@ -445,12 +493,6 @@
<Compile Include="..\Discord.Net\Logging\LogManager.cs"> <Compile Include="..\Discord.Net\Logging\LogManager.cs">
<Link>Logging\LogManager.cs</Link> <Link>Logging\LogManager.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\LogMessageEventArgs.cs">
<Link>LogMessageEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\MessageEventArgs.cs">
<Link>MessageEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\MessageQueue.cs"> <Compile Include="..\Discord.Net\MessageQueue.cs">
<Link>MessageQueue.cs</Link> <Link>MessageQueue.cs</Link>
</Compile> </Compile>
@@ -490,9 +532,15 @@
<Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> <Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs">
<Link>Net\Rest\CompletedRequestEventArgs.cs</Link> <Link>Net\Rest\CompletedRequestEventArgs.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\Rest\ETFRestClient.cs">
<Link>Net\Rest\ETFRestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs">
<Link>Net\Rest\IRestEngine.cs</Link> <Link>Net\Rest\IRestEngine.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\Rest\JsonRestClient.cs">
<Link>Net\Rest\JsonRestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> <Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs">
<Link>Net\Rest\RequestEventArgs.cs</Link> <Link>Net\Rest\RequestEventArgs.cs</Link>
</Compile> </Compile>
@@ -532,27 +580,12 @@
<Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> <Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs">
<Link>Net\WebSockets\WS4NetEngine.cs</Link> <Link>Net\WebSockets\WS4NetEngine.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\ProfileEventArgs.cs">
<Link>ProfileEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\RelativeDirection.cs">
<Link>RelativeDirection.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\RoleEventArgs.cs">
<Link>RoleEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\ServerEventArgs.cs">
<Link>ServerEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\ServiceManager.cs"> <Compile Include="..\Discord.Net\ServiceManager.cs">
<Link>ServiceManager.cs</Link> <Link>ServiceManager.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\TaskManager.cs"> <Compile Include="..\Discord.Net\TaskManager.cs">
<Link>TaskManager.cs</Link> <Link>TaskManager.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\UserEventArgs.cs">
<Link>UserEventArgs.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 1
- 1
src/Discord.Net/API/Client/Common/Channel.cs View File

@@ -5,7 +5,7 @@ namespace Discord.API.Client
{ {
public class Channel : ChannelReference public class Channel : ChannelReference
{ {
public sealed class PermissionOverwrite
public class PermissionOverwrite
{ {
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Common/ExtendedGuild.cs View File

@@ -4,7 +4,7 @@ namespace Discord.API.Client
{ {
public class ExtendedGuild : Guild public class ExtendedGuild : Guild
{ {
public sealed class ExtendedMemberInfo : Member
public class ExtendedMemberInfo : Member
{ {
[JsonProperty("mute")] [JsonProperty("mute")]
public bool? IsServerMuted { get; set; } public bool? IsServerMuted { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Common/Guild.cs View File

@@ -6,7 +6,7 @@ namespace Discord.API.Client
{ {
public class Guild : GuildReference public class Guild : GuildReference
{ {
public sealed class EmojiData
public class EmojiData
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Common/InviteReference.cs View File

@@ -4,7 +4,7 @@ namespace Discord.API.Client
{ {
public class InviteReference public class InviteReference
{ {
public sealed class GuildData : GuildReference
public class GuildData : GuildReference
{ {
[JsonProperty("splash_hash")] [JsonProperty("splash_hash")]
public string Splash { get; set; } public string Splash { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Common/MemberPresence.cs View File

@@ -5,7 +5,7 @@ namespace Discord.API.Client
{ {
public class MemberPresence : MemberReference public class MemberPresence : MemberReference
{ {
public sealed class GameInfo
public class GameInfo
{ {
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }


+ 11
- 11
src/Discord.Net/API/Client/Common/Message.cs View File

@@ -5,7 +5,7 @@ namespace Discord.API.Client
{ {
public class Message : MessageReference public class Message : MessageReference
{ {
public sealed class Attachment
public class Attachment
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
@@ -18,14 +18,14 @@ namespace Discord.API.Client
[JsonProperty("filename")] [JsonProperty("filename")]
public string Filename { get; set; } public string Filename { get; set; }
[JsonProperty("width")] [JsonProperty("width")]
public int Width { get; set; }
public int? Width { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public int Height { get; set; }
public int? Height { get; set; }
} }


public sealed class Embed
public class Embed
{ {
public sealed class Reference
public class Reference
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
@@ -33,25 +33,25 @@ namespace Discord.API.Client
public string Name { get; set; } public string Name { get; set; }
} }


public sealed class ThumbnailInfo
public class ThumbnailInfo
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
[JsonProperty("proxy_url")] [JsonProperty("proxy_url")]
public string ProxyUrl { get; set; } public string ProxyUrl { get; set; }
[JsonProperty("width")] [JsonProperty("width")]
public int Width { get; set; }
public int? Width { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public int Height { get; set; }
public int? Height { get; set; }
} }
public sealed class VideoInfo
public class VideoInfo
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
[JsonProperty("width")] [JsonProperty("width")]
public int Width { get; set; }
public int? Width { get; set; }
[JsonProperty("height")] [JsonProperty("height")]
public int Height { get; set; }
public int? Height { get; set; }
} }


[JsonProperty("url")] [JsonProperty("url")]


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class HeartbeatCommand : IWebSocketMessage
public class HeartbeatCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat;
object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); object IWebSocketMessage.Payload => EpochTime.GetMilliseconds();


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class IdentifyCommand : IWebSocketMessage
public class IdentifyCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Identify; int IWebSocketMessage.OpCode => (int)OpCodes.Identify;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class RequestMembersCommand : IWebSocketMessage
public class RequestMembersCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class ResumeCommand : IWebSocketMessage
public class ResumeCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Resume; int IWebSocketMessage.OpCode => (int)OpCodes.Resume;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;


+ 2
- 2
src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs View File

@@ -3,13 +3,13 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateStatusCommand : IWebSocketMessage
public class UpdateStatusCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false; bool IWebSocketMessage.IsPrivate => false;


public sealed class GameInfo
public class GameInfo
{ {
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateVoiceCommand : IWebSocketMessage
public class UpdateVoiceCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class ChannelCreateEvent : Channel { }
public class ChannelCreateEvent : Channel { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class ChannelDeleteEvent : Channel { }
public class ChannelDeleteEvent : Channel { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class ChannelUpdateEvent : Channel { }
public class ChannelUpdateEvent : Channel { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildBanAddEvent : MemberReference { }
public class GuildBanAddEvent : MemberReference { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildBanRemoveEvent : MemberReference { }
public class GuildBanRemoveEvent : MemberReference { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildCreateEvent : ExtendedGuild { }
public class GuildCreateEvent : ExtendedGuild { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildDeleteEvent : ExtendedGuild { }
public class GuildDeleteEvent : ExtendedGuild { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket.Events namespace Discord.API.Client.GatewaySocket.Events
{ {
//public sealed class GuildEmojisUpdateEvent { }
//public class GuildEmojisUpdateEvent { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
//public sealed class GuildIntegrationsUpdateEvent { }
//public class GuildIntegrationsUpdateEvent { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildMemberAddEvent : Member { }
public class GuildMemberAddEvent : Member { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildMemberRemoveEvent : Member { }
public class GuildMemberRemoveEvent : Member { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildMemberUpdateEvent : Member { }
public class GuildMemberUpdateEvent : Member { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildMembersChunkEvent
public class GuildMembersChunkEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildRoleCreateEvent
public class GuildRoleCreateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildRoleDeleteEvent : RoleReference { }
public class GuildRoleDeleteEvent : RoleReference { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildRoleUpdateEvent
public class GuildRoleUpdateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildUpdateEvent : Guild { }
public class GuildUpdateEvent : Guild { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class MessageAckEvent : MessageReference { }
public class MessageAckEvent : MessageReference { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class MessageCreateEvent : Message { }
public class MessageCreateEvent : Message { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class MessageDeleteEvent : MessageReference { }
public class MessageDeleteEvent : MessageReference { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class MessageUpdateEvent : Message { }
public class MessageUpdateEvent : Message { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class PresenceUpdateEvent : MemberPresence { }
public class PresenceUpdateEvent : MemberPresence { }
} }

+ 2
- 2
src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs View File

@@ -2,9 +2,9 @@


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class ReadyEvent
public class ReadyEvent
{ {
public sealed class ReadState
public class ReadState
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string ChannelId { get; set; } public string ChannelId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs View File

@@ -2,7 +2,7 @@


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class RedirectEvent
public class RedirectEvent
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs View File

@@ -2,7 +2,7 @@


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class ResumedEvent
public class ResumedEvent
{ {
[JsonProperty("heartbeat_interval")] [JsonProperty("heartbeat_interval")]
public int HeartbeatInterval { get; set; } public int HeartbeatInterval { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class TypingStartEvent
public class TypingStartEvent
{ {
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
public ulong UserId { get; set; } public ulong UserId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
//public sealed class UserSettingsUpdateEvent { }
//public class UserSettingsUpdateEvent { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class UserUpdateEvent : User { }
public class UserUpdateEvent : User { }
} }

+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;


namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class VoiceServerUpdateEvent
public class VoiceServerUpdateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs View File

@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class VoiceStateUpdateEvent : MemberVoiceState { }
public class VoiceStateUpdateEvent : MemberVoiceState { }
} }

+ 9
- 0
src/Discord.Net/API/Client/ISerializable.cs View File

@@ -0,0 +1,9 @@
using System.IO;

namespace Discord.API.Client
{
public interface ISerializable
{
void Write(BinaryWriter writer);
}
}

+ 1
- 1
src/Discord.Net/API/Client/IWebSocketMessage.cs View File

@@ -8,7 +8,7 @@ namespace Discord.API.Client
object Payload { get; } object Payload { get; }
bool IsPrivate { get; } bool IsPrivate { get; }
} }
public sealed class WebSocketMessage
public class WebSocketMessage
{ {
[JsonProperty("op")] [JsonProperty("op")]
public int? Operation { get; set; } public int? Operation { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Rest/AcceptInvite.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class AcceptInviteRequest : IRestRequest<InviteReference>
public class AcceptInviteRequest : IRestRequest<InviteReference>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"invite/{InviteId}"; string IRestRequest.Endpoint => $"invite/{InviteId}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/AckMessage.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class AckMessageRequest : IRestRequest
public class AckMessageRequest : IRestRequest
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack";


+ 2
- 2
src/Discord.Net/API/Client/Rest/AddChannelPermission.cs View File

@@ -4,10 +4,10 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class AddChannelPermissionsRequest : IRestRequest
public class AddChannelPermissionsRequest : IRestRequest
{ {
string IRestRequest.Method => "PUT"; string IRestRequest.Method => "PUT";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}";
object IRestRequest.Payload => this; object IRestRequest.Payload => this;
bool IRestRequest.IsPrivate => false; bool IRestRequest.IsPrivate => false;




+ 1
- 1
src/Discord.Net/API/Client/Rest/AddGuildBan.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class AddGuildBanRequest : IRestRequest
public class AddGuildBanRequest : IRestRequest
{ {
string IRestRequest.Method => "PUT"; string IRestRequest.Method => "PUT";
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreateChannel.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class CreateChannelRequest : IRestRequest<Channel>
public class CreateChannelRequest : IRestRequest<Channel>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; string IRestRequest.Endpoint => $"guilds/{GuildId}/channels";


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreateGuild.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class CreateGuildRequest : IRestRequest<Guild>
public class CreateGuildRequest : IRestRequest<Guild>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds"; string IRestRequest.Endpoint => $"guilds";


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreateInvite.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class CreateInviteRequest : IRestRequest<Invite>
public class CreateInviteRequest : IRestRequest<Invite>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; string IRestRequest.Endpoint => $"channels/{ChannelId}/invites";


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class CreatePrivateChannelRequest : IRestRequest<Channel>
public class CreatePrivateChannelRequest : IRestRequest<Channel>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"users/@me/channels"; string IRestRequest.Endpoint => $"users/@me/channels";


+ 1
- 1
src/Discord.Net/API/Client/Rest/CreateRole.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class CreateRoleRequest : IRestRequest<Role>
public class CreateRoleRequest : IRestRequest<Role>
{ {
string IRestRequest.Method => "POST"; string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles";


+ 1
- 1
src/Discord.Net/API/Client/Rest/DeleteChannel.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteChannelRequest : IRestRequest<Channel>
public class DeleteChannelRequest : IRestRequest<Channel>
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"channels/{ChannelId}"; string IRestRequest.Endpoint => $"channels/{ChannelId}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/DeleteInvite.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteInviteRequest : IRestRequest<Invite>
public class DeleteInviteRequest : IRestRequest<Invite>
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"invite/{InviteCode}"; string IRestRequest.Endpoint => $"invite/{InviteCode}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/DeleteMessage.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteMessageRequest : IRestRequest
public class DeleteMessageRequest : IRestRequest
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/DeleteRole.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteRoleRequest : IRestRequest
public class DeleteRoleRequest : IRestRequest
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}";


+ 2
- 2
src/Discord.Net/API/Client/Rest/Gateway.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GatewayRequest : IRestRequest<GatewayResponse>
public class GatewayRequest : IRestRequest<GatewayResponse>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"gateway"; string IRestRequest.Endpoint => $"gateway";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false; bool IRestRequest.IsPrivate => false;
} }
public sealed class GatewayResponse
public class GatewayResponse
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Rest/GetBans.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetBansRequest : IRestRequest<UserReference[]>
public class GetBansRequest : IRestRequest<UserReference[]>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; string IRestRequest.Endpoint => $"guilds/{GuildId}/bans";


+ 1
- 1
src/Discord.Net/API/Client/Rest/GetInvite.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetInviteRequest : IRestRequest<InviteReference>
public class GetInviteRequest : IRestRequest<InviteReference>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"invite/{InviteCode}"; string IRestRequest.Endpoint => $"invite/{InviteCode}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/GetInvites.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetInvitesRequest : IRestRequest<InviteReference[]>
public class GetInvitesRequest : IRestRequest<InviteReference[]>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; string IRestRequest.Endpoint => $"guilds/{GuildId}/invites";


+ 1
- 1
src/Discord.Net/API/Client/Rest/GetMessages.cs View File

@@ -4,7 +4,7 @@ using System.Text;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetMessagesRequest : IRestRequest<Message[]>
public class GetMessagesRequest : IRestRequest<Message[]>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint string IRestRequest.Endpoint


+ 2
- 2
src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]>
public class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"voice/regions"; string IRestRequest.Endpoint => $"voice/regions";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false; bool IRestRequest.IsPrivate => false;
} }
public sealed class GetVoiceRegionsResponse
public class GetVoiceRegionsResponse
{ {
[JsonProperty("sample_hostname")] [JsonProperty("sample_hostname")]
public string Hostname { get; set; } public string Hostname { get; set; }


+ 5
- 5
src/Discord.Net/API/Client/Rest/GetWidget.cs View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetWidgetRequest : IRestRequest<GetWidgetResponse>
public class GetWidgetRequest : IRestRequest<GetWidgetResponse>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json";
@@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest
} }
} }


public sealed class GetWidgetResponse
public class GetWidgetResponse
{ {
public sealed class Channel
public class Channel
{ {
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; } public ulong Id { get; set; }
@@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("position")] [JsonProperty("position")]
public int Position { get; set; } public int Position { get; set; }
} }
public sealed class User : UserReference
public class User : UserReference
{ {
[JsonProperty("avatar_url")] [JsonProperty("avatar_url")]
public string AvatarUrl { get; set; } public string AvatarUrl { get; set; }
@@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("game")] [JsonProperty("game")]
public UserGame Game { get; set; } public UserGame Game { get; set; }
} }
public sealed class UserGame
public class UserGame
{ {
[JsonProperty("id")] [JsonProperty("id")]
public int Id { get; set; } public int Id { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/Rest/KickMember.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class KickMemberRequest : IRestRequest
public class KickMemberRequest : IRestRequest
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}";


+ 1
- 1
src/Discord.Net/API/Client/Rest/LeaveGuild.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class LeaveGuildRequest : IRestRequest<Guild>
public class LeaveGuildRequest : IRestRequest<Guild>
{ {
string IRestRequest.Method => "DELETE"; string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}"; string IRestRequest.Endpoint => $"guilds/{GuildId}";


+ 2
- 2
src/Discord.Net/API/Client/Rest/Login.cs View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class LoginRequest : IRestRequest<LoginResponse>
public class LoginRequest : IRestRequest<LoginResponse>
{ {
string IRestRequest.Method => Email != null ? "POST" : "GET"; string IRestRequest.Method => Email != null ? "POST" : "GET";
string IRestRequest.Endpoint => $"auth/login"; string IRestRequest.Endpoint => $"auth/login";
@@ -16,7 +16,7 @@ namespace Discord.API.Client.Rest
public string Password { get; set; } public string Password { get; set; }
} }


public sealed class LoginResponse
public class LoginResponse
{ {
[JsonProperty("token")] [JsonProperty("token")]
public string Token { get; set; } public string Token { get; set; }


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save