Browse Source

Merge 703fac5e1a into a2c9373ed6

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

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

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

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

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

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
-------

.. 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
==================
===========

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

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

======================= ======= ==============
Flag Type Description
@@ -49,7 +49,7 @@ Otherwise, you can use a single DualChannelPermissions.
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:

@@ -57,7 +57,7 @@ A user's server permissions also contain the default values for it's channel per
Flag Type Description
======================= ======= ==============
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.
ManageChannels Server Manage channels that exist on the server (add, remove them)
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
-------
.. literalinclude:: /samples/permissions.cs
: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
------------

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.

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

* `Discord.Net`_
* `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`_

.. _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
-----
@@ -39,4 +43,4 @@ Example
.. literalinclude:: samples/getting_started.cs
:language: csharp6
:tab-width: 2
:tab-width: 2

+ 13
- 12
docs/index.rst View File

@@ -1,7 +1,7 @@
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.

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 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`_.

.. _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::
:caption: Documentation
:maxdepth: 2
getting_started
features/logging

getting_started
features/modes
features/logging
features/management
features/permissions
features/profile
features/commands
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) =>
{
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
client.Run(async () =>
client.ExecuteAndWait(async () =>
{
//Connect to the Discord server using our email and password
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 (!client.AllServers.Any())
if (!client.Servers.Any())
await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee"));
});
}


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

@@ -3,13 +3,14 @@ class Program
private static DiscordBotClient _client;
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");
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">
<Link>InternalIsSpeakingEventArgs.cs</Link>
</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 Include="..\Discord.Net.Audio\Opus\OpusConverter.cs">
<Link>Opus\OpusConverter.cs</Link>
@@ -74,15 +74,15 @@
<Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs">
<Link>Opus\OpusEncoder.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs">
<Link>SimpleAudioClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs">
<Link>Sodium\SecretBox.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs">
<Link>UserIsTalkingEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\VirtualClient.cs">
<Link>VirtualClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs">
<Link>VoiceBuffer.cs</Link>
</Compile>


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

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

private readonly DiscordConfig _config;
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; }
public int Id { get; }
public AudioService Service { get; }
public AudioServiceConfig Config { get; }
public RestClient ClientAPI { get; }
public GatewaySocket GatewaySocket { get; }
public VoiceWebSocket VoiceSocket { get; }
public VoiceSocket VoiceSocket { get; }
public JsonSerializer Serializer { get; }
public Stream OutputStream { get; }

public CancellationToken CancelToken { get; private set; }
public string SessionId { get; private set; }
public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server;
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.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))
{
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
{
@@ -166,11 +220,11 @@ namespace Discord.Audio
{
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.ChannelId == null)
await Disconnect();
await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value);
@@ -179,7 +233,7 @@ namespace Discord.Audio
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect();
await Disconnect().ConfigureAwait(false);
}
}
}
@@ -187,13 +241,16 @@ namespace Discord.Audio
break;
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
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;
@@ -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)
{
if (data == null) throw new ArgumentException(nameof(data));
@@ -219,29 +273,22 @@ namespace Discord.Audio
VoiceSocket.SendPCMFrames(data, offset, count);
}

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

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

+ 242
- 0
src/Discord.Net.Audio/AudioClient.cs.old View File

@@ -0,0 +1,242 @@
using Discord.API.Client.GatewaySocket;
using Discord.Logging;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Newtonsoft.Json;
using Nito.AsyncEx;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
internal class AudioClient : IAudioClient
{
private class OutStream : Stream
{
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;

private readonly AudioClient _client;

internal OutStream(AudioClient client)
{
_client = client;
}

public override long Length { get { throw new InvalidOperationException(); } }
public override long Position
{
get { throw new InvalidOperationException(); }
set { throw new InvalidOperationException(); }
}
public override void Flush() { throw new InvalidOperationException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); }
public override void SetLength(long value) { throw new InvalidOperationException(); }
public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); }
public override void Write(byte[] buffer, int offset, int count)
{
_client.Send(buffer, offset, count);
}
}
private readonly JsonSerializer _serializer;
private readonly bool _ownsGateway;
private TaskManager _taskManager;
private CancellationToken _cancelToken;

internal AudioService Service { get; }
internal Logger Logger { get; }
public int Id { get; }
public GatewaySocket GatewaySocket { get; }
public VoiceSocket VoiceSocket { get; }
public Stream OutputStream { get; }

public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server;
public Channel Channel => VoiceSocket.Channel;

public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, bool ownsGateway, Logger logger)
{
Service = service;
_serializer = service.Client.Serializer;
Id = clientId;
GatewaySocket = gatewaySocket;
_ownsGateway = ownsGateway;
Logger = logger;
OutputStream = new OutStream(this);
_taskManager = new TaskManager(Cleanup, true);

GatewaySocket.ReceivedDispatch += OnReceivedDispatch;

VoiceSocket = new VoiceSocket(service.Client.Config, service.Config, service.Client.Serializer, logger);
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;
};*/
}

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.");

SendVoiceUpdate(channel.Server.Id, channel.Id);
await Task.Run(() => VoiceSocket.WaitForConnection(_cancelToken));
}
public async Task Connect(RestClient rest = null)
{
var cancelSource = new CancellationTokenSource();
_cancelToken = cancelSource.Token;

Task[] tasks;
if (rest != null)
tasks = new Task[] { GatewaySocket.Connect(rest, _cancelToken) };
else
tasks = new Task[0];

await _taskManager.Start(tasks, cancelSource);
}

public Task Disconnect() => _taskManager.Stop(true);

private async Task Cleanup()
{
var server = VoiceSocket.Server;
VoiceSocket.Server = null;
VoiceSocket.Channel = null;

await Service.RemoveClient(server, this).ConfigureAwait(false);
SendVoiceUpdate(server.Id, null);

await VoiceSocket.Disconnect().ConfigureAwait(false);
if (_ownsGateway)
await GatewaySocket.Disconnect().ConfigureAwait(false);
}

private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{
if (data.ChannelId == null)
await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value);
if (channel != null)
VoiceSocket.Channel = channel;
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect().ConfigureAwait(false);
}
}
}
}
break;
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
var client = Service.Client;
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;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
}

/// <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)
{
if (data == null) throw new ArgumentException(nameof(data));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (VoiceSocket.Server == null) return; //Has been closed
if (count == 0) return;

VoiceSocket.SendPCMFrames(data, offset, count);
}

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

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

public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{
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.Collections.Concurrent;
using System.Linq;
@@ -8,8 +8,10 @@ namespace Discord.Audio
{
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 int _nextClientId;

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

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

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

client.Disconnected += async (s, e) =>
client.GatewaySocket.Disconnected += async (s, e) =>
{
if (Config.EnableMultiserver)
{
@@ -75,68 +79,30 @@ namespace Discord.Audio
{
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
return null;
}
else
{
IAudioClient client;
if (_voiceClients.TryGetValue(server.Id, out client))
return client;
if (server == _currentClient.Server)
return _currentClient;
else
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 (!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();
_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)
{
IAudioClient client;
AudioClient client;
if (_voiceClients.TryRemove(server.Id, out client))
await client.Disconnect().ConfigureAwait(false);
}
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 const int MaxBitrate = 128;

/// <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); } }
private int _connectionTimeout = 30000;
@@ -33,8 +35,8 @@ namespace Discord.Audio
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } }
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;
/// <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); } }


+ 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;

namespace Discord.Audio
{
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; }
/// <summary> Gets the channel this client is currently a member of. </summary>
Channel Channel { get; }
/// <summary> Gets the server this client is bound to. </summary>
Server Server { get; }
/// <summary> Gets a stream object that wraps the Send() function. </summary>
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);
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
Task Disconnect();

/// <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

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

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

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

public string Token { get; internal set; }
public Server Server { get; internal set; }
@@ -57,32 +58,37 @@ namespace Discord.Net.WebSockets
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int 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>();
_targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize];
_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()
{
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)
{
try
{
await Connect().ConfigureAwait(false);
await BeginConnect(_parentCancelToken).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
@@ -90,31 +96,35 @@ namespace Discord.Net.WebSockets
{
Logger.Error("Reconnect failed", ex);
//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) { }
}
public Task Disconnect() => _taskManager.Stop(true);
public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
_userId = null;
}

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

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

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

#if !DOTNET5_4
tasks.Add(WatcherAsync());
#endif
tasks.AddRange(_engine.GetTasks(CancelToken));
tasks.Add(HeartbeatAsync(CancelToken));
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false);
}
protected override async Task Cleanup()
{
@@ -148,7 +158,7 @@ namespace Discord.Net.WebSockets
int packetLength, resultOffset, resultLength;
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);

if ((_config.Mode & AudioMode.Incoming) != 0)
if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
{
decodingBuffer = new byte[MaxOpusSize];
nonce = new byte[24];
@@ -184,7 +194,7 @@ namespace Discord.Net.WebSockets
int port = packet[68] | packet[69] << 8;

SendSelectProtocol(ip, port);
if ((_config.Mode & AudioMode.Incoming) == 0)
if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
return; //We dont need this thread anymore
}
else
@@ -395,7 +405,7 @@ namespace Discord.Net.WebSockets
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
_endpoint = new IPEndPoint(address, payload.Port);

if (_config.EnableEncryption)
if (_audioConfig.EnableEncryption)
{
if (payload.Modes.Contains(EncryptedMode))
{
@@ -467,12 +477,12 @@ namespace Discord.Net.WebSockets

public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand());
public void SendIdentify()
public void SendIdentify(ulong id, string sessionId)
=> QueueMessage(new IdentifyCommand
{
GuildId = Server.Id,
UserId = _client.CurrentUser.Id,
SessionId = _client.SessionId,
UserId = id,
SessionId = sessionId,
Token = Token
});
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
{
public enum OpusApplication : int
internal enum OpusApplication : int
{
Voice = 2048,
MusicOrMixed = 2049,
LowLatency = 2051
}
public enum OpusError : int
internal enum OpusError : int
{
OK = 0,
BadArg = -1,
@@ -22,7 +22,7 @@ namespace Discord.Audio.Opus
AllocFail = -7
}

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


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

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

namespace Discord.Audio.Opus
{
/// <summary> Opus codec wrapper. </summary>
internal class OpusDecoder : OpusConverter
{
/// <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
{
/// <summary> Opus codec wrapper. </summary>
internal class OpusEncoder : OpusConverter
{
/// <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)
: base(samplingRate, channels, frameLength)
{
if (bitrate != null && (bitrate < 1 || bitrate > 512))
if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate))
throw new ArgumentOutOfRangeException(nameof(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" ],

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

"dependencies": {


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

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

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


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

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

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

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)
_checks = new List<IPermissionChecker>(initialChecks);
else
_checks = new List<IPermissionChecker>();
_prefix = prefix;

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

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

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;
_prefix = prefix;
_category = category;
if (initialChecks != null)
_checks = new List<IPermissionChecker>(initialChecks);
else
@@ -145,17 +150,14 @@ namespace Discord.Commands
_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;
}
public CommandBuilder CreateCommand()
=> CreateCommand("");
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<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;
_name = name;
_fullName = fullName;
_items = new Dictionary<string, CommandMap>();
_commands = new List<Command>();
_isVisible = false;
_hasNonAliases = false;
_hasSubGroups = false;
}
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>
Unparsed
}
public sealed class CommandParameter
public class CommandParameter
{
public string Name { get; }
public int Id { get; internal set; }
public ParameterType Type { get; }

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


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

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

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

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



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

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

namespace Discord.Modules
{
public sealed class ModuleManager
public class ModuleManager
{
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { };
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { };
@@ -15,31 +15,31 @@ namespace Discord.Modules
public event EventHandler<ChannelEventArgs> ChannelDisabled = 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> ServerAvailable = delegate { };
public event EventHandler<ChannelEventArgs> ChannelCreated = 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> RoleUpdated = delegate { };
public event EventHandler<RoleUpdatedEventArgs> RoleUpdated = delegate { };
public event EventHandler<RoleEventArgs> RoleDeleted = delegate { };

public event EventHandler<UserEventArgs> UserBanned = delegate { };
public event EventHandler<UserEventArgs> UserJoined = 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<ChannelUserEventArgs> UserIsTypingUpdated = delegate { };
public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { };

public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
public event EventHandler<MessageEventArgs> MessageSent = 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 { };
private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate;
@@ -79,11 +79,12 @@ namespace Discord.Modules
if (_allowAll || _useServerWhitelist) //Server-only events
{
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.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.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.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.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(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.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
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.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">
<Link>API\Status\Rest\UpcomingMaintenances.cs</Link>
</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">
<Link>Config.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DisconnectedEventArgs.cs">
<Link>DisconnectedEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordClient.cs">
<Link>DiscordClient.cs</Link>
</Compile>
@@ -406,6 +397,9 @@
<Compile Include="..\Discord.Net\DiscordConfig.cs">
<Link>DiscordConfig.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DynamicIL.cs">
<Link>DynamicIL.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\ChannelType.cs">
<Link>Enums\ChannelType.cs</Link>
</Compile>
@@ -418,12 +412,66 @@
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs">
<Link>Enums\PermissionTarget.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\Relative.cs">
<Link>Enums\Relative.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\StringEnum.cs">
<Link>Enums\StringEnum.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Enums\UserStatus.cs">
<Link>Enums\UserStatus.cs</Link>
</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">
<Link>Extensions.cs</Link>
</Compile>
@@ -445,12 +493,6 @@
<Compile Include="..\Discord.Net\Logging\LogManager.cs">
<Link>Logging\LogManager.cs</Link>
</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">
<Link>MessageQueue.cs</Link>
</Compile>
@@ -490,9 +532,15 @@
<Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs">
<Link>Net\Rest\CompletedRequestEventArgs.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\ETFRestClient.cs">
<Link>Net\Rest\ETFRestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs">
<Link>Net\Rest\IRestEngine.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\JsonRestClient.cs">
<Link>Net\Rest\JsonRestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs">
<Link>Net\Rest\RequestEventArgs.cs</Link>
</Compile>
@@ -532,27 +580,12 @@
<Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs">
<Link>Net\WebSockets\WS4NetEngine.cs</Link>
</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">
<Link>ServiceManager.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\TaskManager.cs">
<Link>TaskManager.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\UserEventArgs.cs">
<Link>UserEventArgs.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</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 sealed class PermissionOverwrite
public class PermissionOverwrite
{
[JsonProperty("type")]
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 sealed class ExtendedMemberInfo : Member
public class ExtendedMemberInfo : Member
{
[JsonProperty("mute")]
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 sealed class EmojiData
public class EmojiData
{
[JsonProperty("id")]
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 sealed class GuildData : GuildReference
public class GuildData : GuildReference
{
[JsonProperty("splash_hash")]
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 sealed class GameInfo
public class GameInfo
{
[JsonProperty("name")]
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 sealed class Attachment
public class Attachment
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -18,14 +18,14 @@ namespace Discord.API.Client
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonProperty("width")]
public int Width { get; set; }
public int? Width { get; set; }
[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")]
public string Url { get; set; }
@@ -33,25 +33,25 @@ namespace Discord.API.Client
public string Name { get; set; }
}

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

[JsonProperty("url")]


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

@@ -3,7 +3,7 @@
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class HeartbeatCommand : IWebSocketMessage
public class HeartbeatCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat;
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class IdentifyCommand : IWebSocketMessage
public class IdentifyCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Identify;
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class RequestMembersCommand : IWebSocketMessage
public class RequestMembersCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers;
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class ResumeCommand : IWebSocketMessage
public class ResumeCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Resume;
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateStatusCommand : IWebSocketMessage
public class UpdateStatusCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate;
object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false;

public sealed class GameInfo
public class GameInfo
{
[JsonProperty("name")]
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateVoiceCommand : IWebSocketMessage
public class UpdateVoiceCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate;
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
{
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
{
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
{
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
{
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
{
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
{
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
{
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
{
//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
{
//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
{
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
{
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
{
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
{
public sealed class GuildMembersChunkEvent
public class GuildMembersChunkEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
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
{
public sealed class GuildRoleCreateEvent
public class GuildRoleCreateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
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
{
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
{
public sealed class GuildRoleUpdateEvent
public class GuildRoleUpdateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
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
{
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
{
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
{
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
{
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
{
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
{
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
{
public sealed class ReadyEvent
public class ReadyEvent
{
public sealed class ReadState
public class ReadState
{
[JsonProperty("id")]
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
{
public sealed class RedirectEvent
public class RedirectEvent
{
[JsonProperty("url")]
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
{
public sealed class ResumedEvent
public class ResumedEvent
{
[JsonProperty("heartbeat_interval")]
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
{
public sealed class TypingStartEvent
public class TypingStartEvent
{
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
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
{
//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
{
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
{
public sealed class VoiceServerUpdateEvent
public class VoiceServerUpdateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
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
{
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; }
bool IsPrivate { get; }
}
public sealed class WebSocketMessage
public class WebSocketMessage
{
[JsonProperty("op")]
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class AcceptInviteRequest : IRestRequest<InviteReference>
public class AcceptInviteRequest : IRestRequest<InviteReference>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class AckMessageRequest : IRestRequest
public class AckMessageRequest : IRestRequest
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class AddChannelPermissionsRequest : IRestRequest
public class AddChannelPermissionsRequest : IRestRequest
{
string IRestRequest.Method => "PUT";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}";
object IRestRequest.Payload => this;
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class AddGuildBanRequest : IRestRequest
public class AddGuildBanRequest : IRestRequest
{
string IRestRequest.Method => "PUT";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class CreateChannelRequest : IRestRequest<Channel>
public class CreateChannelRequest : IRestRequest<Channel>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class CreateGuildRequest : IRestRequest<Guild>
public class CreateGuildRequest : IRestRequest<Guild>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class CreateInviteRequest : IRestRequest<Invite>
public class CreateInviteRequest : IRestRequest<Invite>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class CreatePrivateChannelRequest : IRestRequest<Channel>
public class CreatePrivateChannelRequest : IRestRequest<Channel>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class CreateRoleRequest : IRestRequest<Role>
public class CreateRoleRequest : IRestRequest<Role>
{
string IRestRequest.Method => "POST";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteChannelRequest : IRestRequest<Channel>
public class DeleteChannelRequest : IRestRequest<Channel>
{
string IRestRequest.Method => "DELETE";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteInviteRequest : IRestRequest<Invite>
public class DeleteInviteRequest : IRestRequest<Invite>
{
string IRestRequest.Method => "DELETE";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteMessageRequest : IRestRequest
public class DeleteMessageRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class DeleteRoleRequest : IRestRequest
public class DeleteRoleRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GatewayRequest : IRestRequest<GatewayResponse>
public class GatewayRequest : IRestRequest<GatewayResponse>
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"gateway";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false;
}
public sealed class GatewayResponse
public class GatewayResponse
{
[JsonProperty("url")]
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetBansRequest : IRestRequest<UserReference[]>
public class GetBansRequest : IRestRequest<UserReference[]>
{
string IRestRequest.Method => "GET";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetInviteRequest : IRestRequest<InviteReference>
public class GetInviteRequest : IRestRequest<InviteReference>
{
string IRestRequest.Method => "GET";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetInvitesRequest : IRestRequest<InviteReference[]>
public class GetInvitesRequest : IRestRequest<InviteReference[]>
{
string IRestRequest.Method => "GET";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetMessagesRequest : IRestRequest<Message[]>
public class GetMessagesRequest : IRestRequest<Message[]>
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint


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

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]>
public class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]>
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"voice/regions";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false;
}
public sealed class GetVoiceRegionsResponse
public class GetVoiceRegionsResponse
{
[JsonProperty("sample_hostname")]
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class GetWidgetRequest : IRestRequest<GetWidgetResponse>
public class GetWidgetRequest : IRestRequest<GetWidgetResponse>
{
string IRestRequest.Method => "GET";
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))]
public ulong Id { get; set; }
@@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("position")]
public int Position { get; set; }
}
public sealed class User : UserReference
public class User : UserReference
{
[JsonProperty("avatar_url")]
public string AvatarUrl { get; set; }
@@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("game")]
public UserGame Game { get; set; }
}
public sealed class UserGame
public class UserGame
{
[JsonProperty("id")]
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class KickMemberRequest : IRestRequest
public class KickMemberRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
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
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class LeaveGuildRequest : IRestRequest<Guild>
public class LeaveGuildRequest : IRestRequest<Guild>
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}";


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

Loading…
Cancel
Save