@@ -1,5 +1,5 @@ | |||||
Commands | |||||
======== | |||||
|stub| Commands | |||||
=============== | |||||
The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands. | The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands. | ||||
@@ -11,10 +11,10 @@ Example (Simple) | |||||
.. literalinclude:: /samples/command.cs | .. literalinclude:: /samples/command.cs | ||||
:language: csharp6 | :language: csharp6 | ||||
:tab-width: 2 | :tab-width: 2 | ||||
Example (Groups) | Example (Groups) | ||||
---------------- | ---------------- | ||||
.. literalinclude:: /samples/command_group.cs | .. literalinclude:: /samples/command_group.cs | ||||
:language: csharp6 | :language: csharp6 | ||||
:tab-width: 2 | |||||
:tab-width: 2 |
@@ -1,75 +1,78 @@ | |||||
|stub| Events | |||||
============= | |||||
Events | |||||
====== | |||||
Usage | Usage | ||||
----- | ----- | ||||
To take advantage of Events in Discord.Net, you need to hook into them. | |||||
There are two ways of hooking into events. See the example for examples on using these events. | |||||
Usable Events | |||||
------------- | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| Event Name | EventArgs | Description | | |||||
+====================+====================+==========================================+ | |||||
| UserBanned | BanEventArgs | Called when a user is banned. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserUnbanned | BanEventArgs | Called when a user is unbanned. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ChannelCreated | ChannelEventArgs | Called when a channel is created. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ChannelDestroyed | ChannelEventArgs | Called when a channel is destroyed. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ChannelUpdated | ChannelEventArgs | Called when a channel is updated. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| MessageReceived | MessageEventArgs | Called when a message is received. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| MessageSent | MessageEventArgs | Called when a message is sent. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| MessageDeleted | MessageEventArgs | Called when a message is deleted. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| MessageUpdated | MessageEventArgs | Called when a message is updated\\edited.| | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| MessageReadRemotely| MessageEventArgs | Called when a message is read. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| RoleCreated | RoleEventArgs | Called when a role is created. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| RoleUpdated | RoleEventArgs | Called when a role is updated. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| RoleDeleted | RoleEventArgs | Called when a role is deleted. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| JoinedServer | ServerEventArgs | Called when a member joins a server. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| LeftServer | ServerEventArgs | Called when a member leaves a server. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ServerUpdated | ServerEventArgs | Called when a server is updated. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ServerUnavailable | ServerEventArgs | Called when a Discord server goes down. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ServerAvailable | ServerEventArgs |Called when a Discord server goes back up.| | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserJoined | UserEventArgs | Called when a user joins a Channel. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserLeft | UserEventArgs | Called when a user leaves a Channel. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserUpdated | UserEventArgs | --- | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserPresenceUpdated| UserEventArgs | Called when a user's presence changes. | | |||||
| | | (Here\\Away) | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserVoiceState | UserEventArgs | Called when a user's voice state changes.| | |||||
| Updated | | (Muted\\Unmuted) | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
|UserIsTypingUpdated | UserEventArgs | Called when a user starts\\stops typing. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| UserIsSpeaking | UserEventArgs | Called when a user's voice state changes.| | |||||
| Updated | | (Speaking\\Not Speaking) | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
| ProfileUpdated | N/A | Called when a user's profile changes. | | |||||
+--------------------+--------------------+------------------------------------------+ | |||||
Example | |||||
------- | |||||
.. literalinclude:: /samples/events.cs | |||||
:language: csharp6 | |||||
:tab-width: 2 | |||||
Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler<EventArgs> C# pattern. | |||||
.. warning:: | |||||
Note that all synchronous code in an event handler will run on the gateway socket's thread and should be handled as quickly as possible. | |||||
Using the async-await pattern to let the thread continue immediately is recommended and is demonstrated in the examples below. | |||||
Connection State | |||||
---------------- | |||||
Connection Events will be raised when the Connection State of your client changes. | |||||
.. warning:: | |||||
You should not use DiscordClient.Connected to run code when your client first connects to Discord. | |||||
If you lose connection and automatically reconnect, this code will be ran again, which may lead to unexpected behavior. | |||||
Messages | |||||
-------- | |||||
- MessageReceived, MessageUpdated and MessageDeleted are raised when a new message arrives, an existing one has been updated (by the user, or by Discord itself), or deleted. | |||||
- MessageAcknowledged is only triggered in client mode, and occurs when a message is read on another device logged-in with your account. | |||||
Example of MessageReceived: | |||||
.. code-block:: c# | |||||
// (Preface: Echo Bots are discouraged, make sure your bot is not running in a public server if you use them) | |||||
// Hook into the MessageReceived event using a Lambda | |||||
_client.MessageReceived += async (s, e) => { | |||||
// Check to make sure that the bot is not the author | |||||
if (!e.Message.IsAuthor) | |||||
// Echo the message back to the channel | |||||
await e.Channel.SendMessage(e.Message); | |||||
}; | |||||
Users | |||||
----- | |||||
There are several user events: | |||||
- UserBanned: A user has been banned from a server. | |||||
- UserUnbanned: A user was unbanned. | |||||
- UserJoined: A user joins a server. | |||||
- UserLeft: A user left (or was kicked from) a server. | |||||
- UserIsTyping: A user in a channel starts typing. | |||||
- UserUpdated: A user object was updated (presence update, role/permission change, or a voice state update). | |||||
.. note:: | |||||
UserUpdated Events include a ``User`` object for Before and After the change. | |||||
When accessing the User, you should only use ``e.Before`` if comparing changes, otherwise use ``e.After`` | |||||
Examples: | |||||
.. code-block:: c# | |||||
// Register a Hook into the UserBanned event using a Lambda | |||||
_client.UserBanned += async (s, e) => { | |||||
// Create a Channel object by searching for a channel named '#logs' on the server the ban occurred in. | |||||
var logChannel = e.Server.FindChannels("logs").FirstOrDefault(); | |||||
// Send a message to the server's log channel, stating that a user was banned. | |||||
await logChannel.SendMessage($"User Banned: {e.User.Name}"); | |||||
}; | |||||
// Register a Hook into the UserUpdated event using a Lambda | |||||
_client.UserUpdated += async (s, e) => { | |||||
// Check that the user is in a Voice channel | |||||
if (e.After.VoiceChannel == null) return; | |||||
// See if they changed Voice channels | |||||
if (e.Before.VoiceChannel == e.After.VoiceChannel) return; | |||||
await logChannel.SendMessage($"User {e.After.Name} changed voice channels!"); | |||||
}; |
@@ -1,11 +1,35 @@ | |||||
|stub| Logging | |||||
============== | |||||
Logging | |||||
======= | |||||
|stub-desc| | |||||
Discord.Net will log all of its events/exceptions using a built-in LogManager. | |||||
This LogManager can be accessed through DiscordClient.Log | |||||
Usage | |||||
----- | |||||
To handle Log Messages through Discord.Net's Logger, you must hook into the Log.Message<LogMessageEventArgs> Event. | |||||
The LogManager does not provide a string-based result for the message, you must put your own message format together using the data provided through LogMessageEventArgs | |||||
See the Example for a snippet of logging. | |||||
Logging Your Own Data | |||||
--------------------- | |||||
The LogManager included in Discord.Net can also be used to log your own messages. | |||||
You can use DiscordClient.Log.Log(LogSeverity, Source, Message, Exception), or one of the shortcut helpers, to log data. | |||||
Example: | |||||
.. code-block:: c# | |||||
_client.MessageReceived += async (s, e) { | |||||
// Log a new Message with Severity Info, Sourced from 'MessageReceived', with the Message Contents. | |||||
_client.Log.Info("MessageReceived", e.Message.Text, null); | |||||
}; | |||||
Example | Example | ||||
------- | ------- | ||||
.. literalinclude:: /samples/logging.cs | .. literalinclude:: /samples/logging.cs | ||||
:language: csharp6 | |||||
:tab-width: 2 | |||||
:language: c# | |||||
:tab-width: 2 |
@@ -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 |
@@ -1,11 +1,11 @@ | |||||
Permissions | Permissions | ||||
================== | |||||
=========== | |||||
There are two types of permissions: *Channel Permissions* and *Server Permissions*. | There are two types of permissions: *Channel Permissions* and *Server Permissions*. | ||||
Channel Permissions | Channel Permissions | ||||
------------------- | ------------------- | ||||
Channel Permissions have a set of bools behind them: | |||||
Channel Permissions are controlled using a set of flags: | |||||
======================= ======= ============== | ======================= ======= ============== | ||||
Flag Type Description | Flag Type Description | ||||
@@ -49,7 +49,7 @@ Otherwise, you can use a single DualChannelPermissions. | |||||
Server Permissions | Server Permissions | ||||
------------------ | ------------------ | ||||
Server permisisons are read-only, you cannot change them. You may still access them, however, using User.GetServerPermissions(); | |||||
Server Permissions can be accessed by ``Server.GetPermissions(User)``, and updated with ``Server.UpdatePermissions(User, ServerPermissions)`` | |||||
A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions: | A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions: | ||||
@@ -57,7 +57,7 @@ A user's server permissions also contain the default values for it's channel per | |||||
Flag Type Description | Flag Type Description | ||||
======================= ======= ============== | ======================= ======= ============== | ||||
BanMembers Server Ban users from the server. | BanMembers Server Ban users from the server. | ||||
KickMembers Server Kick users from the server. They can stil rejoin. | |||||
KickMembers Server Kick users from the server. They can still rejoin. | |||||
ManageRoles Server Manage roles on the server, and their permissions. | ManageRoles Server Manage roles on the server, and their permissions. | ||||
ManageChannels Server Manage channels that exist on the server (add, remove them) | ManageChannels Server Manage channels that exist on the server (add, remove them) | ||||
ManageServer Server Manage the server settings. | ManageServer Server Manage the server settings. | ||||
@@ -69,7 +69,7 @@ Managing permissions for roles is much easier than for users in channels. For ro | |||||
Example | Example | ||||
------- | ------- | ||||
.. literalinclude:: /samples/permissions.cs | .. literalinclude:: /samples/permissions.cs | ||||
:language: csharp6 | :language: csharp6 | ||||
:tab-width: 2 | |||||
:tab-width: 2 |
@@ -1,4 +0,0 @@ | |||||
|stub| Profile | |||||
=================== | |||||
|stub-desc| |
@@ -4,7 +4,7 @@ Getting Started | |||||
Requirements | Requirements | ||||
------------ | ------------ | ||||
Discord.Net currently requires logging in with a claimed account - anonymous logins are not supported. You can `register for a Discord account here`. | |||||
Discord.Net currently requires logging in with a claimed account - anonymous logins are not supported. You can `register for a Discord account here`_. | |||||
New accounts are also useless when not connected to a server, so you should create an invite code for whatever server you intend to test on using the official Discord client. | New accounts are also useless when not connected to a server, so you should create an invite code for whatever server you intend to test on using the official Discord client. | ||||
@@ -17,12 +17,16 @@ You can get Discord.Net from NuGet: | |||||
* `Discord.Net`_ | * `Discord.Net`_ | ||||
* `Discord.Net.Commands`_ | * `Discord.Net.Commands`_ | ||||
* `Discord.Net.Modules`_ | |||||
If you have trouble installing from NuGet, try installing dependencies manually. | |||||
You can also pull the latest source from `GitHub`_ | You can also pull the latest source from `GitHub`_ | ||||
.. _Discord.Net: https://discordapp.com/register | |||||
.. _Discord.Net.Commands: https://discordapp.com/register | |||||
.. _GitHub: https://github.com/RogueException/Discord.Net/> | |||||
.. _Discord.Net: https://www.nuget.org/packages/Discord.Net/0.8.1-beta2 | |||||
.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands/0.8.1-beta2 | |||||
.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules/0.8.1-beta2 | |||||
.. _GitHub: https://github.com/RogueException/Discord.Net/ | |||||
Async | Async | ||||
----- | ----- | ||||
@@ -39,4 +43,4 @@ Example | |||||
.. literalinclude:: samples/getting_started.cs | .. literalinclude:: samples/getting_started.cs | ||||
:language: csharp6 | :language: csharp6 | ||||
:tab-width: 2 | |||||
:tab-width: 2 |
@@ -1,7 +1,7 @@ | |||||
Discord.Net | Discord.Net | ||||
=========== | =========== | ||||
Discord.Net is an unofficial C# wrapper around the `Discord chat service`. | |||||
Discord.Net is an unofficial C# wrapper around the `Discord Chat Service`. | |||||
It offers several methods to create automated operations, bots, or even custom clients. | It offers several methods to create automated operations, bots, or even custom clients. | ||||
Feel free to join us in the `Discord API chat`_. | Feel free to join us in the `Discord API chat`_. | ||||
@@ -9,28 +9,29 @@ Feel free to join us in the `Discord API chat`_. | |||||
.. _Discord chat service: https://discordapp.com | .. _Discord chat service: https://discordapp.com | ||||
.. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx | .. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx | ||||
Warning | |||||
------- | |||||
.. warn:: | |||||
This is an alpha! | |||||
This is a beta! | |||||
This library has been built thanks to a community effort reverse engineering the Discord client. | |||||
As Discord is still in alpha, it may change at any time without notice, breaking this library as well. | |||||
Discord.Net itself is also in early development and you will often encounter breaking changes until the official Discord API is released. | |||||
This library has been built thanks to a community effort reverse engineering the Discord client. | |||||
As the API is still unofficial, it may change at any time without notice, breaking this library as well. | |||||
Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released. | |||||
It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_. | It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_. | ||||
.. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx | .. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx | ||||
This Documentation is **currently undergoing a rewrite**. Some pages (marked with a wrench) are not updated, or are not completed yet. | |||||
.. toctree:: | .. toctree:: | ||||
:caption: Documentation | :caption: Documentation | ||||
:maxdepth: 2 | :maxdepth: 2 | ||||
getting_started | |||||
features/logging | |||||
getting_started | |||||
features/modes | |||||
features/logging | |||||
features/management | features/management | ||||
features/permissions | features/permissions | ||||
features/profile | |||||
features/commands | features/commands | ||||
features/voice | features/voice | ||||
features/events | |||||
features/events |
@@ -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 | |||||
}); |
@@ -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")); | |||||
}); | |||||
}); |
@@ -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); |
@@ -11,17 +11,17 @@ class Program | |||||
client.MessageReceived += async (s, e) => | client.MessageReceived += async (s, e) => | ||||
{ | { | ||||
if (!e.Message.IsAuthor) | if (!e.Message.IsAuthor) | ||||
await client.SendMessage(e.Channel, e.Message.Text); | |||||
await e.Channel.SendMessage(e.Message.Text); | |||||
}; | }; | ||||
//Convert our sync method to an async one and block the Main function until the bot disconnects | //Convert our sync method to an async one and block the Main function until the bot disconnects | ||||
client.Run(async () => | |||||
client.ExecuteAndWait(async () => | |||||
{ | { | ||||
//Connect to the Discord server using our email and password | //Connect to the Discord server using our email and password | ||||
await client.Connect("discordtest@email.com", "Password123"); | await client.Connect("discordtest@email.com", "Password123"); | ||||
//If we are not a member of any server, use our invite code (made beforehand in the official Discord Client) | //If we are not a member of any server, use our invite code (made beforehand in the official Discord Client) | ||||
if (!client.AllServers.Any()) | |||||
if (!client.Servers.Any()) | |||||
await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee")); | await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee")); | ||||
}); | }); | ||||
} | } | ||||
@@ -3,13 +3,14 @@ class Program | |||||
private static DiscordBotClient _client; | private static DiscordBotClient _client; | ||||
static void Main(string[] args) | static void Main(string[] args) | ||||
{ | { | ||||
var client = new DiscordClient(new DiscordClientConfig { | |||||
//Warning: Debug mode should only be used for identifying problems. It _will_ slow your application down. | |||||
LogLevel = LogMessageSeverity.Debug | |||||
}); | |||||
client.LogMessage += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}"); | |||||
client.Run(async () => | |||||
var client = new DiscordClient(x => | |||||
{ | |||||
LogLevel = LogSeverity.Info | |||||
}); | |||||
_client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}"); | |||||
client.ExecuteAndWait(async () => | |||||
{ | { | ||||
await client.Connect("discordtest@email.com", "Password123"); | await client.Connect("discordtest@email.com", "Password123"); | ||||
if (!client.Servers.Any()) | if (!client.Servers.Any()) | ||||
@@ -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); | |||||
} |
@@ -62,8 +62,8 @@ | |||||
<Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> | <Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> | ||||
<Link>InternalIsSpeakingEventArgs.cs</Link> | <Link>InternalIsSpeakingEventArgs.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\Net\VoiceWebSocket.cs"> | |||||
<Link>Net\VoiceWebSocket.cs</Link> | |||||
<Compile Include="..\Discord.Net.Audio\Net\VoiceSocket.cs"> | |||||
<Link>Net\VoiceSocket.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> | ||||
<Link>Opus\OpusConverter.cs</Link> | <Link>Opus\OpusConverter.cs</Link> | ||||
@@ -74,15 +74,15 @@ | |||||
<Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | ||||
<Link>Opus\OpusEncoder.cs</Link> | <Link>Opus\OpusEncoder.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs"> | |||||
<Link>SimpleAudioClient.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | ||||
<Link>Sodium\SecretBox.cs</Link> | <Link>Sodium\SecretBox.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | <Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | ||||
<Link>UserIsTalkingEventArgs.cs</Link> | <Link>UserIsTalkingEventArgs.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\VirtualClient.cs"> | |||||
<Link>VirtualClient.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | ||||
<Link>VoiceBuffer.cs</Link> | <Link>VoiceBuffer.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -1,9 +1,12 @@ | |||||
using Discord.API.Client.GatewaySocket; | using Discord.API.Client.GatewaySocket; | ||||
using Discord.API.Client.Rest; | |||||
using Discord.Logging; | using Discord.Logging; | ||||
using Discord.Net.Rest; | |||||
using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Nito.AsyncEx; | using Nito.AsyncEx; | ||||
using System; | using System; | ||||
using System.Diagnostics; | |||||
using System.IO; | using System.IO; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -41,124 +44,175 @@ namespace Discord.Audio | |||||
} | } | ||||
} | } | ||||
private readonly DiscordConfig _config; | |||||
private readonly AsyncLock _connectionLock; | private readonly AsyncLock _connectionLock; | ||||
private readonly JsonSerializer _serializer; | |||||
private CancellationTokenSource _cancelTokenSource; | |||||
private readonly TaskManager _taskManager; | |||||
private ConnectionState _gatewayState; | |||||
internal AudioService Service { get; } | |||||
internal Logger Logger { get; } | internal Logger Logger { get; } | ||||
public int Id { get; } | public int Id { get; } | ||||
public AudioService Service { get; } | |||||
public AudioServiceConfig Config { get; } | |||||
public RestClient ClientAPI { get; } | |||||
public GatewaySocket GatewaySocket { get; } | public GatewaySocket GatewaySocket { get; } | ||||
public VoiceWebSocket VoiceSocket { get; } | |||||
public VoiceSocket VoiceSocket { get; } | |||||
public JsonSerializer Serializer { get; } | |||||
public Stream OutputStream { get; } | public Stream OutputStream { get; } | ||||
public CancellationToken CancelToken { get; private set; } | |||||
public string SessionId { get; private set; } | |||||
public ConnectionState State => VoiceSocket.State; | public ConnectionState State => VoiceSocket.State; | ||||
public Server Server => VoiceSocket.Server; | public Server Server => VoiceSocket.Server; | ||||
public Channel Channel => VoiceSocket.Channel; | public Channel Channel => VoiceSocket.Channel; | ||||
public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger) | |||||
public AudioClient(DiscordClient client, Server server, int id) | |||||
{ | { | ||||
Service = service; | |||||
_serializer = service.Client.Serializer; | |||||
Id = clientId; | |||||
GatewaySocket = gatewaySocket; | |||||
Logger = logger; | |||||
OutputStream = new OutStream(this); | |||||
Id = id; | |||||
_config = client.Config; | |||||
Service = client.Audio(); | |||||
Config = Service.Config; | |||||
Serializer = client.Serializer; | |||||
_gatewayState = (int)ConnectionState.Disconnected; | |||||
_connectionLock = new AsyncLock(); | |||||
//Logging | |||||
Logger = client.Log.CreateLogger($"AudioClient #{id}"); | |||||
GatewaySocket.ReceivedDispatch += OnReceivedDispatch; | |||||
//Async | |||||
_taskManager = new TaskManager(Cleanup, false); | |||||
_connectionLock = new AsyncLock(); | |||||
CancelToken = new CancellationToken(true); | |||||
VoiceSocket = new VoiceWebSocket(service.Client, this, logger); | |||||
//Networking | |||||
if (Config.EnableMultiserver) | |||||
{ | |||||
ClientAPI = new JsonRestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}")); | |||||
GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}")); | |||||
GatewaySocket.Connected += (s, e) => | |||||
{ | |||||
if (_gatewayState == ConnectionState.Connecting) | |||||
EndGatewayConnect(); | |||||
}; | |||||
} | |||||
else | |||||
GatewaySocket = client.GatewaySocket; | |||||
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | |||||
VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); | |||||
VoiceSocket.Server = server; | VoiceSocket.Server = server; | ||||
/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); | |||||
_voiceSocket.Disconnected += async (s, e) => | |||||
{ | |||||
_voiceSocket.CurrentServerId; | |||||
if (voiceServerId != null) | |||||
_gatewaySocket.SendLeaveVoice(voiceServerId.Value); | |||||
await _voiceSocket.Disconnect().ConfigureAwait(false); | |||||
RaiseVoiceDisconnected(socket.CurrentServerId.Value, e); | |||||
if (e.WasUnexpected) | |||||
await socket.Reconnect().ConfigureAwait(false); | |||||
};*/ | |||||
/*_voiceSocket.IsSpeaking += (s, e) => | |||||
{ | |||||
if (_voiceSocket.State == WebSocketState.Connected) | |||||
{ | |||||
var user = _users[e.UserId, socket.CurrentServerId]; | |||||
bool value = e.IsSpeaking; | |||||
if (user.IsSpeaking != value) | |||||
{ | |||||
user.IsSpeaking = value; | |||||
var channel = _channels[_voiceSocket.CurrentChannelId]; | |||||
RaiseUserIsSpeaking(user, channel, value); | |||||
if (Config.TrackActivity) | |||||
user.UpdateActivity(); | |||||
} | |||||
} | |||||
};*/ | |||||
/*this.Connected += (s, e) => | |||||
{ | |||||
_voiceSocket.ParentCancelToken = _cancelToken; | |||||
};*/ | |||||
OutputStream = new OutStream(this); | |||||
} | } | ||||
public async Task Join(Channel channel) | |||||
{ | |||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
if (channel.Type != ChannelType.Voice) | |||||
throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); | |||||
if (channel.Server != VoiceSocket.Server) | |||||
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); | |||||
if (channel == VoiceSocket.Channel) return; | |||||
if (VoiceSocket.Server == null) | |||||
throw new InvalidOperationException("This client has been closed."); | |||||
using (await _connectionLock.LockAsync().ConfigureAwait(false)) | |||||
public async Task Connect() | |||||
{ | |||||
if (Config.EnableMultiserver) | |||||
await BeginGatewayConnect().ConfigureAwait(false); | |||||
else | |||||
{ | { | ||||
VoiceSocket.Channel = channel; | |||||
await Task.Run(() => | |||||
var cancelSource = new CancellationTokenSource(); | |||||
CancelToken = cancelSource.Token; | |||||
await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); | |||||
} | |||||
} | |||||
private async Task BeginGatewayConnect() | |||||
{ | |||||
try | |||||
{ | |||||
using (await _connectionLock.LockAsync().ConfigureAwait(false)) | |||||
{ | { | ||||
SendVoiceUpdate(); | |||||
VoiceSocket.WaitForConnection(_cancelTokenSource.Token); | |||||
}); | |||||
await Disconnect().ConfigureAwait(false); | |||||
_taskManager.ClearException(); | |||||
ClientAPI.Token = Service.Client.ClientAPI.Token; | |||||
Stopwatch stopwatch = null; | |||||
if (_config.LogLevel >= LogSeverity.Verbose) | |||||
stopwatch = Stopwatch.StartNew(); | |||||
_gatewayState = ConnectionState.Connecting; | |||||
var cancelSource = new CancellationTokenSource(); | |||||
CancelToken = cancelSource.Token; | |||||
ClientAPI.CancelToken = CancelToken; | |||||
await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); | |||||
await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); | |||||
GatewaySocket.WaitForConnection(CancelToken); | |||||
if (_config.LogLevel >= LogSeverity.Verbose) | |||||
{ | |||||
stopwatch.Stop(); | |||||
double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); | |||||
Logger.Verbose($"Connection took {seconds} sec"); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
await _taskManager.SignalError(ex).ConfigureAwait(false); | |||||
throw; | |||||
} | } | ||||
} | } | ||||
private void EndGatewayConnect() | |||||
{ | |||||
_gatewayState = ConnectionState.Connected; | |||||
} | |||||
public async Task Connect(bool connectGateway) | |||||
public async Task Disconnect() | |||||
{ | { | ||||
using (await _connectionLock.LockAsync().ConfigureAwait(false)) | |||||
{ | |||||
_cancelTokenSource = new CancellationTokenSource(); | |||||
var cancelToken = _cancelTokenSource.Token; | |||||
VoiceSocket.ParentCancelToken = cancelToken; | |||||
await _taskManager.Stop(true).ConfigureAwait(false); | |||||
if (Config.EnableMultiserver) | |||||
ClientAPI.Token = null; | |||||
} | |||||
private async Task Cleanup() | |||||
{ | |||||
var oldState = _gatewayState; | |||||
_gatewayState = ConnectionState.Disconnecting; | |||||
if (connectGateway) | |||||
if (Config.EnableMultiserver) | |||||
{ | |||||
if (oldState == ConnectionState.Connected) | |||||
{ | { | ||||
GatewaySocket.ParentCancelToken = cancelToken; | |||||
await GatewaySocket.Connect().ConfigureAwait(false); | |||||
GatewaySocket.WaitForConnection(cancelToken); | |||||
try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } | |||||
catch (OperationCanceledException) { } | |||||
} | } | ||||
await GatewaySocket.Disconnect().ConfigureAwait(false); | |||||
ClientAPI.Token = null; | |||||
} | } | ||||
var server = VoiceSocket.Server; | |||||
VoiceSocket.Server = null; | |||||
VoiceSocket.Channel = null; | |||||
if (Config.EnableMultiserver) | |||||
await Service.RemoveClient(server, this).ConfigureAwait(false); | |||||
SendVoiceUpdate(server.Id, null); | |||||
await VoiceSocket.Disconnect().ConfigureAwait(false); | |||||
if (Config.EnableMultiserver) | |||||
await GatewaySocket.Disconnect().ConfigureAwait(false); | |||||
_gatewayState = (int)ConnectionState.Disconnected; | |||||
} | } | ||||
public async Task Disconnect() | |||||
public async Task Join(Channel channel) | |||||
{ | { | ||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
if (channel.Type != ChannelType.Voice) | |||||
throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); | |||||
if (channel == VoiceSocket.Channel) return; | |||||
var server = channel.Server; | |||||
if (server != VoiceSocket.Server) | |||||
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); | |||||
if (VoiceSocket.Server == null) | |||||
throw new InvalidOperationException("This client has been closed."); | |||||
SendVoiceUpdate(channel.Server.Id, channel.Id); | |||||
using (await _connectionLock.LockAsync().ConfigureAwait(false)) | using (await _connectionLock.LockAsync().ConfigureAwait(false)) | ||||
{ | |||||
await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false); | |||||
VoiceSocket.Channel = null; | |||||
SendVoiceUpdate(); | |||||
await VoiceSocket.Disconnect(); | |||||
} | |||||
await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)); | |||||
} | } | ||||
private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | |||||
private async void OnReceivedEvent(WebSocketEventEventArgs e) | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
@@ -166,11 +220,11 @@ namespace Discord.Audio | |||||
{ | { | ||||
case "VOICE_STATE_UPDATE": | case "VOICE_STATE_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer); | |||||
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(Serializer); | |||||
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) | if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) | ||||
{ | { | ||||
if (data.ChannelId == null) | if (data.ChannelId == null) | ||||
await Disconnect(); | |||||
await Disconnect().ConfigureAwait(false); | |||||
else | else | ||||
{ | { | ||||
var channel = Service.Client.GetChannel(data.ChannelId.Value); | var channel = Service.Client.GetChannel(data.ChannelId.Value); | ||||
@@ -179,7 +233,7 @@ namespace Discord.Audio | |||||
else | else | ||||
{ | { | ||||
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); | Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); | ||||
await Disconnect(); | |||||
await Disconnect().ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -187,13 +241,16 @@ namespace Discord.Audio | |||||
break; | break; | ||||
case "VOICE_SERVER_UPDATE": | case "VOICE_SERVER_UPDATE": | ||||
{ | { | ||||
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer); | |||||
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer); | |||||
if (data.GuildId == VoiceSocket.Server?.Id) | if (data.GuildId == VoiceSocket.Server?.Id) | ||||
{ | { | ||||
var client = Service.Client; | var client = Service.Client; | ||||
VoiceSocket.Token = data.Token; | |||||
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||||
await VoiceSocket.Connect().ConfigureAwait(false); | |||||
var id = client.CurrentUser?.Id; | |||||
if (id != null) | |||||
{ | |||||
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||||
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
@@ -205,9 +262,6 @@ namespace Discord.Audio | |||||
} | } | ||||
} | } | ||||
/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> | |||||
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param> | |||||
/// <param name="count">Number of bytes in this frame. </param> | |||||
public void Send(byte[] data, int offset, int count) | public void Send(byte[] data, int offset, int count) | ||||
{ | { | ||||
if (data == null) throw new ArgumentException(nameof(data)); | if (data == null) throw new ArgumentException(nameof(data)); | ||||
@@ -219,29 +273,22 @@ namespace Discord.Audio | |||||
VoiceSocket.SendPCMFrames(data, offset, count); | VoiceSocket.SendPCMFrames(data, offset, count); | ||||
} | } | ||||
/// <summary> Clears the PCM buffer. </summary> | |||||
public void Clear() | public void Clear() | ||||
{ | { | ||||
if (VoiceSocket.Server == null) return; //Has been closed | if (VoiceSocket.Server == null) return; //Has been closed | ||||
VoiceSocket.ClearPCMFrames(); | VoiceSocket.ClearPCMFrames(); | ||||
} | } | ||||
/// <summary> Returns a task that completes once the voice output buffer is empty. </summary> | |||||
public void Wait() | public void Wait() | ||||
{ | { | ||||
if (VoiceSocket.Server == null) return; //Has been closed | if (VoiceSocket.Server == null) return; //Has been closed | ||||
VoiceSocket.WaitForQueue(); | VoiceSocket.WaitForQueue(); | ||||
} | } | ||||
private void SendVoiceUpdate() | |||||
public void SendVoiceUpdate(ulong? serverId, ulong? channelId) | |||||
{ | { | ||||
var serverId = VoiceSocket.Server?.Id; | |||||
if (serverId != null) | |||||
{ | |||||
GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id, | |||||
(Service.Config.Mode | AudioMode.Outgoing) == 0, | |||||
(Service.Config.Mode | AudioMode.Incoming) == 0); | |||||
} | |||||
GatewaySocket.SendUpdateVoice(serverId, channelId, | |||||
(Service.Config.Mode | AudioMode.Outgoing) == 0, | |||||
(Service.Config.Mode | AudioMode.Incoming) == 0); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1,4 +1,4 @@ | |||||
using Discord.Net.WebSockets; | |||||
using Nito.AsyncEx; | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Linq; | using System.Linq; | ||||
@@ -8,8 +8,10 @@ namespace Discord.Audio | |||||
{ | { | ||||
public class AudioService : IService | public class AudioService : IService | ||||
{ | { | ||||
private AudioClient _defaultClient; | |||||
private ConcurrentDictionary<ulong, IAudioClient> _voiceClients; | |||||
private readonly AsyncLock _asyncLock; | |||||
private AudioClient _defaultClient; //Only used for single server | |||||
private VirtualClient _currentClient; //Only used for single server | |||||
private ConcurrentDictionary<ulong, AudioClient> _voiceClients; | |||||
private ConcurrentDictionary<User, bool> _talkingUsers; | private ConcurrentDictionary<User, bool> _talkingUsers; | ||||
private int _nextClientId; | private int _nextClientId; | ||||
@@ -30,22 +32,24 @@ namespace Discord.Audio | |||||
public AudioService(AudioServiceConfig config) | public AudioService(AudioServiceConfig config) | ||||
{ | { | ||||
Config = config; | Config = config; | ||||
} | |||||
_asyncLock = new AsyncLock(); | |||||
} | |||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
Client = client; | Client = client; | ||||
Config.Lock(); | Config.Lock(); | ||||
if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
_voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); | |||||
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>(); | |||||
else | else | ||||
{ | { | ||||
var logger = Client.Log.CreateLogger("Voice"); | var logger = Client.Log.CreateLogger("Voice"); | ||||
_defaultClient = new SimpleAudioClient(this, 0, logger); | |||||
_defaultClient = new AudioClient(Client, null, 0); | |||||
} | } | ||||
_talkingUsers = new ConcurrentDictionary<User, bool>(); | _talkingUsers = new ConcurrentDictionary<User, bool>(); | ||||
client.Disconnected += async (s, e) => | |||||
client.GatewaySocket.Disconnected += async (s, e) => | |||||
{ | { | ||||
if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
{ | { | ||||
@@ -75,68 +79,30 @@ namespace Discord.Audio | |||||
{ | { | ||||
if (server == null) throw new ArgumentNullException(nameof(server)); | if (server == null) throw new ArgumentNullException(nameof(server)); | ||||
if (!Config.EnableMultiserver) | |||||
if (Config.EnableMultiserver) | |||||
{ | { | ||||
if (server == _defaultClient.Server) | |||||
return (_defaultClient as SimpleAudioClient).CurrentClient; | |||||
AudioClient client; | |||||
if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
return client; | |||||
else | else | ||||
return null; | return null; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
IAudioClient client; | |||||
if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
return client; | |||||
if (server == _currentClient.Server) | |||||
return _currentClient; | |||||
else | else | ||||
return null; | return null; | ||||
} | } | ||||
} | } | ||||
private async Task<IAudioClient> CreateClient(Server server) | |||||
{ | |||||
var client = _voiceClients.GetOrAdd(server.Id, _ => null); //Placeholder, so we can't have two clients connecting at once | |||||
if (client == null) | |||||
{ | |||||
int id = unchecked(++_nextClientId); | |||||
var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}"); | |||||
var voiceLogger = Client.Log.CreateLogger($"Voice #{id}"); | |||||
var gatewaySocket = new GatewaySocket(Client, gatewayLogger); | |||||
var voiceClient = new AudioClient(this, id, server, Client.GatewaySocket, voiceLogger); | |||||
await voiceClient.Connect(true).ConfigureAwait(false); | |||||
/*voiceClient.VoiceSocket.FrameReceived += (s, e) => | |||||
{ | |||||
OnFrameReceieved(e); | |||||
}; | |||||
voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => | |||||
{ | |||||
var user = server.GetUser(e.UserId); | |||||
OnUserIsSpeakingUpdated(user, e.IsSpeaking); | |||||
};*/ | |||||
//Update the placeholder only it still exists (RemoveClient wasnt called) | |||||
if (!_voiceClients.TryUpdate(server.Id, voiceClient, null)) | |||||
{ | |||||
//If it was, cleanup | |||||
await voiceClient.Disconnect().ConfigureAwait(false); ; | |||||
await gatewaySocket.Disconnect().ConfigureAwait(false); ; | |||||
} | |||||
} | |||||
return client; | |||||
} | |||||
//TODO: This isn't threadsafe | |||||
internal async Task RemoveClient(Server server, IAudioClient client) | |||||
//Called from AudioClient.Disconnect | |||||
internal async Task RemoveClient(Server server, AudioClient client) | |||||
{ | { | ||||
if (Config.EnableMultiserver && server != null) | |||||
using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
{ | { | ||||
if (_voiceClients.TryRemove(server.Id, out client)) | |||||
{ | |||||
await client.Disconnect(); | |||||
await (client as AudioClient).GatewaySocket.Disconnect(); | |||||
} | |||||
if (_voiceClients.TryUpdate(server.Id, null, client)) | |||||
_voiceClients.TryRemove(server.Id, out client); | |||||
} | } | ||||
} | } | ||||
@@ -144,16 +110,48 @@ namespace Discord.Audio | |||||
{ | { | ||||
if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
if (!Config.EnableMultiserver) | |||||
{ | |||||
await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false); | |||||
return _defaultClient; | |||||
} | |||||
else | |||||
var server = channel.Server; | |||||
using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
{ | { | ||||
var client = await CreateClient(channel.Server).ConfigureAwait(false); | |||||
await client.Join(channel).ConfigureAwait(false); | |||||
return client; | |||||
if (Config.EnableMultiserver) | |||||
{ | |||||
AudioClient client; | |||||
if (!_voiceClients.TryGetValue(server.Id, out client)) | |||||
{ | |||||
client = new AudioClient(Client, server, unchecked(++_nextClientId)); | |||||
_voiceClients[server.Id] = client; | |||||
await client.Connect().ConfigureAwait(false); | |||||
/*voiceClient.VoiceSocket.FrameReceived += (s, e) => | |||||
{ | |||||
OnFrameReceieved(e); | |||||
}; | |||||
voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => | |||||
{ | |||||
var user = server.GetUser(e.UserId); | |||||
OnUserIsSpeakingUpdated(user, e.IsSpeaking); | |||||
};*/ | |||||
} | |||||
await client.Join(channel).ConfigureAwait(false); | |||||
return client; | |||||
} | |||||
else | |||||
{ | |||||
if (_defaultClient.Server != server) | |||||
{ | |||||
await _defaultClient.Disconnect().ConfigureAwait(false); | |||||
_defaultClient.VoiceSocket.Server = server; | |||||
await _defaultClient.Connect().ConfigureAwait(false); | |||||
} | |||||
var client = new VirtualClient(_defaultClient, server); | |||||
_currentClient = client; | |||||
await client.Join(channel).ConfigureAwait(false); | |||||
return client; | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -163,15 +161,18 @@ namespace Discord.Audio | |||||
if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
{ | { | ||||
IAudioClient client; | |||||
AudioClient client; | |||||
if (_voiceClients.TryRemove(server.Id, out client)) | if (_voiceClients.TryRemove(server.Id, out client)) | ||||
await client.Disconnect().ConfigureAwait(false); | await client.Disconnect().ConfigureAwait(false); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
IAudioClient client = GetClient(server); | |||||
if (client != null) | |||||
await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false); | |||||
using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
{ | |||||
var client = GetClient(server) as VirtualClient; | |||||
if (client != null) | |||||
await _defaultClient.Disconnect().ConfigureAwait(false); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -12,6 +12,8 @@ namespace Discord.Audio | |||||
public class AudioServiceConfig | public class AudioServiceConfig | ||||
{ | { | ||||
public const int MaxBitrate = 128; | |||||
/// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary> | /// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary> | ||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | ||||
private int _connectionTimeout = 30000; | private int _connectionTimeout = 30000; | ||||
@@ -33,8 +35,8 @@ namespace Discord.Audio | |||||
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } | public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } | ||||
private int _bufferLength = 1000; | private int _bufferLength = 1000; | ||||
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and 512 inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary> | |||||
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } } | |||||
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary> | |||||
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } } | |||||
private int? _bitrate = null; | private int? _bitrate = null; | ||||
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary> | /// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary> | ||||
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } } | public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } } | ||||
@@ -1,16 +1,38 @@ | |||||
using System.IO; | |||||
using Discord.Net.Rest; | |||||
using Discord.Net.WebSockets; | |||||
using System.IO; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
public interface IAudioClient | public interface IAudioClient | ||||
{ | { | ||||
/// <summary> Gets the unique identifier for this client. </summary> | |||||
int Id { get; } | |||||
/// <summary> Gets the session id for the current connection. </summary> | |||||
string SessionId { get; } | |||||
/// <summary> Gets the current state of this client. </summary> | |||||
ConnectionState State { get; } | ConnectionState State { get; } | ||||
/// <summary> Gets the channel this client is currently a member of. </summary> | |||||
Channel Channel { get; } | Channel Channel { get; } | ||||
/// <summary> Gets the server this client is bound to. </summary> | |||||
Server Server { get; } | Server Server { get; } | ||||
/// <summary> Gets a stream object that wraps the Send() function. </summary> | |||||
Stream OutputStream { get; } | Stream OutputStream { get; } | ||||
/// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary> | |||||
CancellationToken CancelToken { get; } | |||||
/// <summary> Gets the internal RestClient for the Client API endpoint. </summary> | |||||
RestClient ClientAPI { get; } | |||||
/// <summary> Gets the internal WebSocket for the Gateway event stream. </summary> | |||||
GatewaySocket GatewaySocket { get; } | |||||
/// <summary> Gets the internal WebSocket for the Voice control stream. </summary> | |||||
VoiceSocket VoiceSocket { get; } | |||||
/// <summary> Moves the client to another channel on the same server. </summary> | |||||
Task Join(Channel channel); | Task Join(Channel channel); | ||||
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary> | |||||
Task Disconnect(); | Task Disconnect(); | ||||
/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> | /// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary> | ||||
@@ -10,6 +10,7 @@ using System; | |||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.IO; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Net; | using System.Net; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
@@ -19,7 +20,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
public partial class VoiceWebSocket : WebSocket | |||||
public partial class VoiceSocket : WebSocket | |||||
{ | { | ||||
private const int MaxOpusSize = 4000; | private const int MaxOpusSize = 4000; | ||||
private const string EncryptedMode = "xsalsa20_poly1305"; | private const string EncryptedMode = "xsalsa20_poly1305"; | ||||
@@ -27,8 +28,7 @@ namespace Discord.Net.WebSockets | |||||
private readonly int _targetAudioBufferLength; | private readonly int _targetAudioBufferLength; | ||||
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; | ||||
private readonly AudioClient _audioClient; | |||||
private readonly AudioServiceConfig _config; | |||||
private readonly AudioServiceConfig _audioConfig; | |||||
private Task _sendTask, _receiveTask; | private Task _sendTask, _receiveTask; | ||||
private VoiceBuffer _sendBuffer; | private VoiceBuffer _sendBuffer; | ||||
private OpusEncoder _encoder; | private OpusEncoder _encoder; | ||||
@@ -41,6 +41,8 @@ namespace Discord.Net.WebSockets | |||||
private ushort _sequence; | private ushort _sequence; | ||||
private string _encryptionMode; | private string _encryptionMode; | ||||
private int _ping; | private int _ping; | ||||
private ulong? _userId; | |||||
private string _sessionId; | |||||
public string Token { get; internal set; } | public string Token { get; internal set; } | ||||
public Server Server { get; internal set; } | public Server Server { get; internal set; } | ||||
@@ -57,32 +59,37 @@ namespace Discord.Net.WebSockets | |||||
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) | ||||
=> FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); | => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); | ||||
internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, Logger logger) | |||||
: base(client, logger) | |||||
internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger) | |||||
: base(config, serializer, logger) | |||||
{ | { | ||||
_audioClient = audioClient; | |||||
_config = client.Audio().Config; | |||||
_audioConfig = audioConfig; | |||||
_decoders = new ConcurrentDictionary<uint, OpusDecoder>(); | _decoders = new ConcurrentDictionary<uint, OpusDecoder>(); | ||||
_targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames | |||||
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames | |||||
_encodingBuffer = new byte[MaxOpusSize]; | _encodingBuffer = new byte[MaxOpusSize]; | ||||
_ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | _ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | ||||
_encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed); | |||||
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | |||||
_encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed); | |||||
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | |||||
} | } | ||||
public Task Connect() | |||||
=> BeginConnect(); | |||||
public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken) | |||||
{ | |||||
Host = host; | |||||
Token = token; | |||||
_userId = userId; | |||||
_sessionId = sessionId; | |||||
return BeginConnect(parentCancelToken); | |||||
} | |||||
private async Task Reconnect() | private async Task Reconnect() | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
var cancelToken = ParentCancelToken.Value; | |||||
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
var cancelToken = _parentCancelToken; | |||||
await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
await Connect().ConfigureAwait(false); | |||||
await BeginConnect(_parentCancelToken).ConfigureAwait(false); | |||||
break; | break; | ||||
} | } | ||||
catch (OperationCanceledException) { throw; } | catch (OperationCanceledException) { throw; } | ||||
@@ -90,40 +97,52 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
Logger.Error("Reconnect failed", ex); | Logger.Error("Reconnect failed", ex); | ||||
//Net is down? We can keep trying to reconnect until the user runs Disconnect() | //Net is down? We can keep trying to reconnect until the user runs Disconnect() | ||||
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
} | } | ||||
public Task Disconnect() => _taskManager.Stop(true); | |||||
public async Task Disconnect() | |||||
{ | |||||
await _taskManager.Stop(true).ConfigureAwait(false); | |||||
_userId = null; | |||||
} | |||||
protected override async Task Run() | protected override async Task Run() | ||||
{ | { | ||||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | ||||
List<Task> tasks = new List<Task>(); | List<Task> tasks = new List<Task>(); | ||||
if (_config.Mode.HasFlag(AudioMode.Outgoing)) | |||||
if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing)) | |||||
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); | _sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); | ||||
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); | _receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); | ||||
SendIdentify(); | |||||
SendIdentify(_userId.Value, _sessionId); | |||||
#if !DOTNET5_4 | #if !DOTNET5_4 | ||||
tasks.Add(WatcherAsync()); | tasks.Add(WatcherAsync()); | ||||
#endif | #endif | ||||
tasks.AddRange(_engine.GetTasks(CancelToken)); | tasks.AddRange(_engine.GetTasks(CancelToken)); | ||||
tasks.Add(HeartbeatAsync(CancelToken)); | tasks.Add(HeartbeatAsync(CancelToken)); | ||||
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); | |||||
await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false); | |||||
} | } | ||||
protected override async Task Cleanup() | protected override async Task Cleanup() | ||||
{ | { | ||||
var sendThread = _sendTask; | var sendThread = _sendTask; | ||||
if (sendThread != null) await sendThread; | |||||
if (sendThread != null) | |||||
{ | |||||
try { await sendThread.ConfigureAwait(false); } | |||||
catch (Exception) { } //Ignore any errors during cleanup | |||||
} | |||||
_sendTask = null; | _sendTask = null; | ||||
var receiveThread = _receiveTask; | var receiveThread = _receiveTask; | ||||
if (receiveThread != null) await receiveThread; | |||||
if (receiveThread != null) | |||||
{ | |||||
try { await receiveThread.ConfigureAwait(false); } | |||||
catch (Exception) { } //Ignore any errors during cleanup | |||||
} | |||||
_receiveTask = null; | _receiveTask = null; | ||||
OpusDecoder decoder; | OpusDecoder decoder; | ||||
@@ -136,7 +155,7 @@ namespace Discord.Net.WebSockets | |||||
ClearPCMFrames(); | ClearPCMFrames(); | ||||
_udp = null; | _udp = null; | ||||
await base.Cleanup(); | |||||
await base.Cleanup().ConfigureAwait(false); | |||||
} | } | ||||
private async Task ReceiveVoiceAsync(CancellationToken cancelToken) | private async Task ReceiveVoiceAsync(CancellationToken cancelToken) | ||||
@@ -148,7 +167,7 @@ namespace Discord.Net.WebSockets | |||||
int packetLength, resultOffset, resultLength; | int packetLength, resultOffset, resultLength; | ||||
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); | IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); | ||||
if ((_config.Mode & AudioMode.Incoming) != 0) | |||||
if ((_audioConfig.Mode & AudioMode.Incoming) != 0) | |||||
{ | { | ||||
decodingBuffer = new byte[MaxOpusSize]; | decodingBuffer = new byte[MaxOpusSize]; | ||||
nonce = new byte[24]; | nonce = new byte[24]; | ||||
@@ -156,7 +175,7 @@ namespace Discord.Net.WebSockets | |||||
while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
await Task.Delay(1); | |||||
await Task.Delay(1).ConfigureAwait(false); | |||||
if (_udp.Available > 0) | if (_udp.Available > 0) | ||||
{ | { | ||||
#if !DOTNET5_4 | #if !DOTNET5_4 | ||||
@@ -184,7 +203,7 @@ namespace Discord.Net.WebSockets | |||||
int port = packet[68] | packet[69] << 8; | int port = packet[68] | packet[69] << 8; | ||||
SendSelectProtocol(ip, port); | SendSelectProtocol(ip, port); | ||||
if ((_config.Mode & AudioMode.Incoming) == 0) | |||||
if ((_audioConfig.Mode & AudioMode.Incoming) == 0) | |||||
return; //We dont need this thread anymore | return; //We dont need this thread anymore | ||||
} | } | ||||
else | else | ||||
@@ -246,7 +265,7 @@ namespace Discord.Net.WebSockets | |||||
try | try | ||||
{ | { | ||||
while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) | while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) | ||||
await Task.Delay(1); | |||||
await Task.Delay(1).ConfigureAwait(false); | |||||
if (cancelToken.IsCancellationRequested) | if (cancelToken.IsCancellationRequested) | ||||
return; | return; | ||||
@@ -362,10 +381,10 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); | int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); | ||||
if (time > 0) | if (time > 0) | ||||
await Task.Delay(time); | |||||
await Task.Delay(time).ConfigureAwait(false); | |||||
} | } | ||||
else | else | ||||
await Task.Delay(1); //Give as much time to the encrypter as possible | |||||
await Task.Delay(1).ConfigureAwait(false); //Give as much time to the encrypter as possible | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -374,14 +393,21 @@ namespace Discord.Net.WebSockets | |||||
} | } | ||||
#if !DOTNET5_4 | #if !DOTNET5_4 | ||||
//Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken | //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken | ||||
private Task WatcherAsync() | |||||
=> CancelToken.Wait().ContinueWith(_ => _udp.Close()); | |||||
private async Task WatcherAsync() | |||||
{ | |||||
await CancelToken.Wait(); | |||||
_udp.Close(); | |||||
} | |||||
#endif | #endif | ||||
protected override async Task ProcessMessage(string json) | protected override async Task ProcessMessage(string json) | ||||
{ | { | ||||
await base.ProcessMessage(json).ConfigureAwait(false); | await base.ProcessMessage(json).ConfigureAwait(false); | ||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||||
WebSocketMessage msg; | |||||
using (var reader = new JsonTextReader(new StringReader(json))) | |||||
msg = _serializer.Deserialize(reader, typeof(WebSocketMessage)) as WebSocketMessage; | |||||
var opCode = (OpCodes)msg.Operation; | var opCode = (OpCodes)msg.Operation; | ||||
switch (opCode) | switch (opCode) | ||||
{ | { | ||||
@@ -395,7 +421,7 @@ namespace Discord.Net.WebSockets | |||||
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); | var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); | ||||
_endpoint = new IPEndPoint(address, payload.Port); | _endpoint = new IPEndPoint(address, payload.Port); | ||||
if (_config.EnableEncryption) | |||||
if (_audioConfig.EnableEncryption) | |||||
{ | { | ||||
if (payload.Modes.Contains(EncryptedMode)) | if (payload.Modes.Contains(EncryptedMode)) | ||||
{ | { | ||||
@@ -436,7 +462,7 @@ namespace Discord.Net.WebSockets | |||||
var payload = (msg.Payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | var payload = (msg.Payload as JToken).ToObject<SessionDescriptionEvent>(_serializer); | ||||
_secretKey = payload.SecretKey; | _secretKey = payload.SecretKey; | ||||
SendSetSpeaking(true); | SendSetSpeaking(true); | ||||
await EndConnect(); | |||||
await EndConnect().ConfigureAwait(false); | |||||
} | } | ||||
break; | break; | ||||
case OpCodes.Speaking: | case OpCodes.Speaking: | ||||
@@ -467,12 +493,12 @@ namespace Discord.Net.WebSockets | |||||
public override void SendHeartbeat() | public override void SendHeartbeat() | ||||
=> QueueMessage(new HeartbeatCommand()); | => QueueMessage(new HeartbeatCommand()); | ||||
public void SendIdentify() | |||||
public void SendIdentify(ulong id, string sessionId) | |||||
=> QueueMessage(new IdentifyCommand | => QueueMessage(new IdentifyCommand | ||||
{ | { | ||||
GuildId = Server.Id, | GuildId = Server.Id, | ||||
UserId = _client.CurrentUser.Id, | |||||
SessionId = _client.SessionId, | |||||
UserId = id, | |||||
SessionId = sessionId, | |||||
Token = Token | Token = Token | ||||
}); | }); | ||||
public void SendSelectProtocol(string externalAddress, int externalPort) | public void SendSelectProtocol(string externalAddress, int externalPort) |
@@ -4,13 +4,13 @@ using System.Security; | |||||
namespace Discord.Audio.Opus | namespace Discord.Audio.Opus | ||||
{ | { | ||||
public enum OpusApplication : int | |||||
internal enum OpusApplication : int | |||||
{ | { | ||||
Voice = 2048, | Voice = 2048, | ||||
MusicOrMixed = 2049, | MusicOrMixed = 2049, | ||||
LowLatency = 2051 | LowLatency = 2051 | ||||
} | } | ||||
public enum OpusError : int | |||||
internal enum OpusError : int | |||||
{ | { | ||||
OK = 0, | OK = 0, | ||||
BadArg = -1, | BadArg = -1, | ||||
@@ -22,7 +22,7 @@ namespace Discord.Audio.Opus | |||||
AllocFail = -7 | AllocFail = -7 | ||||
} | } | ||||
public abstract class OpusConverter : IDisposable | |||||
internal abstract class OpusConverter : IDisposable | |||||
{ | { | ||||
protected enum Ctl : int | protected enum Ctl : int | ||||
{ | { | ||||
@@ -2,7 +2,6 @@ | |||||
namespace Discord.Audio.Opus | namespace Discord.Audio.Opus | ||||
{ | { | ||||
/// <summary> Opus codec wrapper. </summary> | |||||
internal class OpusDecoder : OpusConverter | internal class OpusDecoder : OpusConverter | ||||
{ | { | ||||
/// <summary> Creates a new Opus decoder. </summary> | /// <summary> Creates a new Opus decoder. </summary> | ||||
@@ -2,7 +2,6 @@ | |||||
namespace Discord.Audio.Opus | namespace Discord.Audio.Opus | ||||
{ | { | ||||
/// <summary> Opus codec wrapper. </summary> | |||||
internal class OpusEncoder : OpusConverter | internal class OpusEncoder : OpusConverter | ||||
{ | { | ||||
/// <summary> Gets the bit rate in kbit/s. </summary> | /// <summary> Gets the bit rate in kbit/s. </summary> | ||||
@@ -19,7 +18,7 @@ namespace Discord.Audio.Opus | |||||
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) | public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) | ||||
: base(samplingRate, channels, frameLength) | : base(samplingRate, channels, frameLength) | ||||
{ | { | ||||
if (bitrate != null && (bitrate < 1 || bitrate > 512)) | |||||
if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate)) | |||||
throw new ArgumentOutOfRangeException(nameof(bitrate)); | throw new ArgumentOutOfRangeException(nameof(bitrate)); | ||||
BitRate = bitrate; | BitRate = bitrate; | ||||
@@ -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; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -13,8 +13,8 @@ | |||||
"contentFiles": [ "libsodium.dll", "opus.dll" ], | "contentFiles": [ "libsodium.dll", "opus.dll" ], | ||||
"compilationOptions": { | "compilationOptions": { | ||||
"warningsAsErrors": true, | |||||
"allowUnsafe": true | |||||
"allowUnsafe": true, | |||||
"warningsAsErrors": true | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
@@ -5,7 +5,8 @@ using System.Threading.Tasks; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
public sealed class Command | |||||
//TODO: Make this more friendly and expose it to be extendable | |||||
public class Command | |||||
{ | { | ||||
private string[] _aliases; | private string[] _aliases; | ||||
internal CommandParameter[] _parameters; | internal CommandParameter[] _parameters; | ||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
//TODO: Make this more friendly and expose it to be extendable | |||||
public sealed class CommandBuilder | public sealed class CommandBuilder | ||||
{ | { | ||||
private readonly CommandService _service; | private readonly CommandService _service; | ||||
@@ -18,17 +19,20 @@ namespace Discord.Commands | |||||
public CommandService Service => _service; | public CommandService Service => _service; | ||||
internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) | |||||
internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) | |||||
{ | { | ||||
_service = service; | |||||
_command = command; | |||||
_command.Category = category; | |||||
_params = new List<CommandParameter>(); | |||||
_service = service; | |||||
_prefix = prefix; | |||||
_command = new Command(AppendPrefix(prefix, text)); | |||||
_command.Category = category; | |||||
if (initialChecks != null) | if (initialChecks != null) | ||||
_checks = new List<IPermissionChecker>(initialChecks); | _checks = new List<IPermissionChecker>(initialChecks); | ||||
else | else | ||||
_checks = new List<IPermissionChecker>(); | _checks = new List<IPermissionChecker>(); | ||||
_prefix = prefix; | |||||
_params = new List<CommandParameter>(); | |||||
_aliases = new List<string>(); | _aliases = new List<string>(); | ||||
_allowRequiredParams = true; | _allowRequiredParams = true; | ||||
@@ -112,7 +116,7 @@ namespace Discord.Commands | |||||
return prefix; | return prefix; | ||||
} | } | ||||
} | } | ||||
public sealed class CommandGroupBuilder | |||||
public class CommandGroupBuilder | |||||
{ | { | ||||
private readonly CommandService _service; | private readonly CommandService _service; | ||||
private readonly string _prefix; | private readonly string _prefix; | ||||
@@ -121,10 +125,11 @@ namespace Discord.Commands | |||||
public CommandService Service => _service; | public CommandService Service => _service; | ||||
internal CommandGroupBuilder(CommandService service, string prefix, IEnumerable<IPermissionChecker> initialChecks = null) | |||||
internal CommandGroupBuilder(CommandService service, string prefix = "", string category = null, IEnumerable<IPermissionChecker> initialChecks = null) | |||||
{ | { | ||||
_service = service; | _service = service; | ||||
_prefix = prefix; | _prefix = prefix; | ||||
_category = category; | |||||
if (initialChecks != null) | if (initialChecks != null) | ||||
_checks = new List<IPermissionChecker>(initialChecks); | _checks = new List<IPermissionChecker>(initialChecks); | ||||
else | else | ||||
@@ -145,17 +150,14 @@ namespace Discord.Commands | |||||
_checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); | ||||
} | } | ||||
public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
{ | |||||
config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _checks)); | |||||
public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config) | |||||
{ | |||||
config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _category, _checks)); | |||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder CreateCommand() | public CommandBuilder CreateCommand() | ||||
=> CreateCommand(""); | => CreateCommand(""); | ||||
public CommandBuilder CreateCommand(string cmd) | public CommandBuilder CreateCommand(string cmd) | ||||
{ | |||||
var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | |||||
return new CommandBuilder(_service, command, _prefix, _category, _checks); | |||||
} | |||||
=> new CommandBuilder(_service, cmd, _prefix, _category, _checks); | |||||
} | } | ||||
} | } |
@@ -20,16 +20,20 @@ namespace Discord.Commands | |||||
public IEnumerable<Command> Commands => _commands; | public IEnumerable<Command> Commands => _commands; | ||||
public IEnumerable<CommandMap> SubGroups => _items.Values; | public IEnumerable<CommandMap> SubGroups => _items.Values; | ||||
public CommandMap(CommandMap parent, string name, string fullName) | |||||
public CommandMap() | |||||
{ | |||||
_items = new Dictionary<string, CommandMap>(); | |||||
_commands = new List<Command>(); | |||||
_isVisible = false; | |||||
_hasNonAliases = false; | |||||
_hasSubGroups = false; | |||||
} | |||||
public CommandMap(CommandMap parent, string name, string fullName) | |||||
: this() | |||||
{ | { | ||||
_parent = parent; | _parent = parent; | ||||
_name = name; | _name = name; | ||||
_fullName = fullName; | _fullName = fullName; | ||||
_items = new Dictionary<string, CommandMap>(); | |||||
_commands = new List<Command>(); | |||||
_isVisible = false; | |||||
_hasNonAliases = false; | |||||
_hasSubGroups = false; | |||||
} | } | ||||
public CommandMap GetItem(string text) | public CommandMap GetItem(string text) | ||||
@@ -11,13 +11,13 @@ | |||||
/// <summary> Catches all remaining text as a single optional parameter. </summary> | /// <summary> Catches all remaining text as a single optional parameter. </summary> | ||||
Unparsed | Unparsed | ||||
} | } | ||||
public sealed class CommandParameter | |||||
public class CommandParameter | |||||
{ | { | ||||
public string Name { get; } | public string Name { get; } | ||||
public int Id { get; internal set; } | public int Id { get; internal set; } | ||||
public ParameterType Type { get; } | public ParameterType Type { get; } | ||||
public CommandParameter(string name, ParameterType type) | |||||
internal CommandParameter(string name, ParameterType type) | |||||
{ | { | ||||
Name = name; | Name = name; | ||||
Type = type; | Type = type; | ||||
@@ -34,9 +34,9 @@ namespace Discord.Commands | |||||
Config = config; | Config = config; | ||||
_allCommands = new List<Command>(); | _allCommands = new List<Command>(); | ||||
_map = new CommandMap(null, "", ""); | |||||
_map = new CommandMap(); | |||||
_categories = new Dictionary<string, CommandMap>(); | _categories = new Dictionary<string, CommandMap>(); | ||||
Root = new CommandGroupBuilder(this, "", null); | |||||
Root = new CommandGroupBuilder(this); | |||||
} | } | ||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
@@ -309,7 +309,7 @@ namespace Discord.Commands | |||||
string categoryName = command.Category ?? ""; | string categoryName = command.Category ?? ""; | ||||
if (!_categories.TryGetValue(categoryName, out category)) | if (!_categories.TryGetValue(categoryName, out category)) | ||||
{ | { | ||||
category = new CommandMap(null, "", ""); | |||||
category = new CommandMap(); | |||||
_categories.Add(categoryName, category); | _categories.Add(categoryName, category); | ||||
} | } | ||||
@@ -7,7 +7,7 @@ using System.Linq; | |||||
namespace Discord.Modules | namespace Discord.Modules | ||||
{ | { | ||||
public sealed class ModuleManager | |||||
public class ModuleManager | |||||
{ | { | ||||
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; | ||||
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; | ||||
@@ -15,31 +15,31 @@ namespace Discord.Modules | |||||
public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { }; | public event EventHandler<ChannelEventArgs> ChannelDisabled = delegate { }; | ||||
public event EventHandler<ServerEventArgs> LeftServer = delegate { }; | public event EventHandler<ServerEventArgs> LeftServer = delegate { }; | ||||
public event EventHandler<ServerEventArgs> ServerUpdated = delegate { }; | |||||
public event EventHandler<ServerUpdatedEventArgs> ServerUpdated = delegate { }; | |||||
public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; | public event EventHandler<ServerEventArgs> ServerUnavailable = delegate { }; | ||||
public event EventHandler<ServerEventArgs> ServerAvailable = delegate { }; | public event EventHandler<ServerEventArgs> ServerAvailable = delegate { }; | ||||
public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { }; | public event EventHandler<ChannelEventArgs> ChannelCreated = delegate { }; | ||||
public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { }; | public event EventHandler<ChannelEventArgs> ChannelDestroyed = delegate { }; | ||||
public event EventHandler<ChannelEventArgs> ChannelUpdated = delegate { }; | |||||
public event EventHandler<ChannelUpdatedEventArgs> ChannelUpdated = delegate { }; | |||||
public event EventHandler<RoleEventArgs> RoleCreated = delegate { }; | public event EventHandler<RoleEventArgs> RoleCreated = delegate { }; | ||||
public event EventHandler<RoleEventArgs> RoleUpdated = delegate { }; | |||||
public event EventHandler<RoleUpdatedEventArgs> RoleUpdated = delegate { }; | |||||
public event EventHandler<RoleEventArgs> RoleDeleted = delegate { }; | public event EventHandler<RoleEventArgs> RoleDeleted = delegate { }; | ||||
public event EventHandler<UserEventArgs> UserBanned = delegate { }; | public event EventHandler<UserEventArgs> UserBanned = delegate { }; | ||||
public event EventHandler<UserEventArgs> UserJoined = delegate { }; | public event EventHandler<UserEventArgs> UserJoined = delegate { }; | ||||
public event EventHandler<UserEventArgs> UserLeft = delegate { }; | public event EventHandler<UserEventArgs> UserLeft = delegate { }; | ||||
public event EventHandler<UserEventArgs> UserUpdated = delegate { }; | |||||
public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { }; | |||||
public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { }; | |||||
public event EventHandler<UserUpdatedEventArgs> UserUpdated = delegate { }; | |||||
//public event EventHandler<UserEventArgs> UserPresenceUpdated = delegate { }; | |||||
//public event EventHandler<UserEventArgs> UserVoiceStateUpdated = delegate { }; | |||||
public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; | public event EventHandler<UserEventArgs> UserUnbanned = delegate { }; | ||||
public event EventHandler<ChannelUserEventArgs> UserIsTypingUpdated = delegate { }; | |||||
public event EventHandler<ChannelUserEventArgs> UserIsTyping = delegate { }; | |||||
public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; | public event EventHandler<MessageEventArgs> MessageReceived = delegate { }; | ||||
public event EventHandler<MessageEventArgs> MessageSent = delegate { }; | public event EventHandler<MessageEventArgs> MessageSent = delegate { }; | ||||
public event EventHandler<MessageEventArgs> MessageDeleted = delegate { }; | public event EventHandler<MessageEventArgs> MessageDeleted = delegate { }; | ||||
public event EventHandler<MessageEventArgs> MessageUpdated = delegate { }; | |||||
public event EventHandler<MessageUpdatedEventArgs> MessageUpdated = delegate { }; | |||||
public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { }; | public event EventHandler<MessageEventArgs> MessageReadRemotely = delegate { }; | ||||
private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; | private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; | ||||
@@ -79,11 +79,12 @@ namespace Discord.Modules | |||||
if (_allowAll || _useServerWhitelist) //Server-only events | if (_allowAll || _useServerWhitelist) //Server-only events | ||||
{ | { | ||||
client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; | client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; | ||||
client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; | |||||
//TODO: This *is* a channel update if the before/after voice channel is whitelisted | |||||
//client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; | |||||
} | } | ||||
client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); }; | client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); }; | ||||
client.ChannelUpdated += (s, e) => { if (HasChannel(e.Channel)) ChannelUpdated(s, e); }; | |||||
client.ChannelUpdated += (s, e) => { if (HasChannel(e.After)) ChannelUpdated(s, e); }; | |||||
client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); }; | client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); }; | ||||
client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); }; | client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); }; | ||||
@@ -96,16 +97,16 @@ namespace Discord.Modules | |||||
client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); }; | client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); }; | ||||
client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } }; | client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } }; | ||||
client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.Server)) ServerUpdated(s, e); }; | |||||
client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.After)) ServerUpdated(s, e); }; | |||||
client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); }; | client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); }; | ||||
client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); }; | client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); }; | ||||
client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; | client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; | ||||
client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; | client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; | ||||
client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; | client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; | ||||
client.UserIsTypingUpdated += (s, e) => { if (HasChannel(e.Channel)) UserIsTypingUpdated(s, e); }; | |||||
client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; | |||||
//TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist | //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist | ||||
client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; | |||||
//client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; | |||||
client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; | client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; | ||||
client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; | client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; | ||||
} | } | ||||
@@ -385,18 +385,9 @@ | |||||
<Compile Include="..\Discord.Net\API\Status\Rest\UpcomingMaintenances.cs"> | <Compile Include="..\Discord.Net\API\Status\Rest\UpcomingMaintenances.cs"> | ||||
<Link>API\Status\Rest\UpcomingMaintenances.cs</Link> | <Link>API\Status\Rest\UpcomingMaintenances.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\ChannelEventArgs.cs"> | |||||
<Link>ChannelEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ChannelUserEventArgs.cs"> | |||||
<Link>ChannelUserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Config.cs"> | <Compile Include="..\Discord.Net\Config.cs"> | ||||
<Link>Config.cs</Link> | <Link>Config.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\DisconnectedEventArgs.cs"> | |||||
<Link>DisconnectedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\DiscordClient.cs"> | <Compile Include="..\Discord.Net\DiscordClient.cs"> | ||||
<Link>DiscordClient.cs</Link> | <Link>DiscordClient.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -406,6 +397,9 @@ | |||||
<Compile Include="..\Discord.Net\DiscordConfig.cs"> | <Compile Include="..\Discord.Net\DiscordConfig.cs"> | ||||
<Link>DiscordConfig.cs</Link> | <Link>DiscordConfig.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\DynamicIL.cs"> | |||||
<Link>DynamicIL.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Enums\ChannelType.cs"> | <Compile Include="..\Discord.Net\Enums\ChannelType.cs"> | ||||
<Link>Enums\ChannelType.cs</Link> | <Link>Enums\ChannelType.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -418,12 +412,66 @@ | |||||
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> | <Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> | ||||
<Link>Enums\PermissionTarget.cs</Link> | <Link>Enums\PermissionTarget.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Enums\Relative.cs"> | |||||
<Link>Enums\Relative.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Enums\StringEnum.cs"> | <Compile Include="..\Discord.Net\Enums\StringEnum.cs"> | ||||
<Link>Enums\StringEnum.cs</Link> | <Link>Enums\StringEnum.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | <Compile Include="..\Discord.Net\Enums\UserStatus.cs"> | ||||
<Link>Enums\UserStatus.cs</Link> | <Link>Enums\UserStatus.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\ETF\ETFReader.cs"> | |||||
<Link>ETF\ETFReader.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ETF\ETFType.cs"> | |||||
<Link>ETF\ETFType.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ETF\ETFWriter.cs"> | |||||
<Link>ETF\ETFWriter.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ChannelEventArgs.cs"> | |||||
<Link>Events\ChannelEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ChannelUpdatedEventArgs.cs"> | |||||
<Link>Events\ChannelUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ChannelUserEventArgs.cs"> | |||||
<Link>Events\ChannelUserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\DisconnectedEventArgs.cs"> | |||||
<Link>Events\DisconnectedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\LogMessageEventArgs.cs"> | |||||
<Link>Events\LogMessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\MessageEventArgs.cs"> | |||||
<Link>Events\MessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\MessageUpdatedEventArgs.cs"> | |||||
<Link>Events\MessageUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ProfileUpdatedEventArgs.cs"> | |||||
<Link>Events\ProfileUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\RoleEventArgs.cs"> | |||||
<Link>Events\RoleEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\RoleUpdatedEventArgs.cs"> | |||||
<Link>Events\RoleUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ServerEventArgs.cs"> | |||||
<Link>Events\ServerEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\ServerUpdatedEventArgs.cs"> | |||||
<Link>Events\ServerUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\UserEventArgs.cs"> | |||||
<Link>Events\UserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Events\UserUpdatedEventArgs.cs"> | |||||
<Link>Events\UserUpdatedEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Extensions.cs"> | <Compile Include="..\Discord.Net\Extensions.cs"> | ||||
<Link>Extensions.cs</Link> | <Link>Extensions.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -445,12 +493,6 @@ | |||||
<Compile Include="..\Discord.Net\Logging\LogManager.cs"> | <Compile Include="..\Discord.Net\Logging\LogManager.cs"> | ||||
<Link>Logging\LogManager.cs</Link> | <Link>Logging\LogManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\LogMessageEventArgs.cs"> | |||||
<Link>LogMessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\MessageEventArgs.cs"> | |||||
<Link>MessageEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\MessageQueue.cs"> | <Compile Include="..\Discord.Net\MessageQueue.cs"> | ||||
<Link>MessageQueue.cs</Link> | <Link>MessageQueue.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -490,9 +532,15 @@ | |||||
<Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> | <Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> | ||||
<Link>Net\Rest\CompletedRequestEventArgs.cs</Link> | <Link>Net\Rest\CompletedRequestEventArgs.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\Rest\ETFRestClient.cs"> | |||||
<Link>Net\Rest\ETFRestClient.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | ||||
<Link>Net\Rest\IRestEngine.cs</Link> | <Link>Net\Rest\IRestEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\Rest\JsonRestClient.cs"> | |||||
<Link>Net\Rest\JsonRestClient.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | <Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | ||||
<Link>Net\Rest\RequestEventArgs.cs</Link> | <Link>Net\Rest\RequestEventArgs.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -532,27 +580,12 @@ | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\WS4NetEngine.cs"> | ||||
<Link>Net\WebSockets\WS4NetEngine.cs</Link> | <Link>Net\WebSockets\WS4NetEngine.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\ProfileEventArgs.cs"> | |||||
<Link>ProfileEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\RelativeDirection.cs"> | |||||
<Link>RelativeDirection.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\RoleEventArgs.cs"> | |||||
<Link>RoleEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ServerEventArgs.cs"> | |||||
<Link>ServerEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\ServiceManager.cs"> | <Compile Include="..\Discord.Net\ServiceManager.cs"> | ||||
<Link>ServiceManager.cs</Link> | <Link>ServiceManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\TaskManager.cs"> | <Compile Include="..\Discord.Net\TaskManager.cs"> | ||||
<Link>TaskManager.cs</Link> | <Link>TaskManager.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\UserEventArgs.cs"> | |||||
<Link>UserEventArgs.cs</Link> | |||||
</Compile> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class Channel : ChannelReference | public class Channel : ChannelReference | ||||
{ | { | ||||
public sealed class PermissionOverwrite | |||||
public class PermissionOverwrite | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public string Type { get; set; } | public string Type { get; set; } | ||||
@@ -4,7 +4,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class ExtendedGuild : Guild | public class ExtendedGuild : Guild | ||||
{ | { | ||||
public sealed class ExtendedMemberInfo : Member | |||||
public class ExtendedMemberInfo : Member | |||||
{ | { | ||||
[JsonProperty("mute")] | [JsonProperty("mute")] | ||||
public bool? IsServerMuted { get; set; } | public bool? IsServerMuted { get; set; } | ||||
@@ -6,7 +6,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class Guild : GuildReference | public class Guild : GuildReference | ||||
{ | { | ||||
public sealed class EmojiData | |||||
public class EmojiData | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public string Id { get; set; } | public string Id { get; set; } | ||||
@@ -4,7 +4,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class InviteReference | public class InviteReference | ||||
{ | { | ||||
public sealed class GuildData : GuildReference | |||||
public class GuildData : GuildReference | |||||
{ | { | ||||
[JsonProperty("splash_hash")] | [JsonProperty("splash_hash")] | ||||
public string Splash { get; set; } | public string Splash { get; set; } | ||||
@@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class MemberPresence : MemberReference | public class MemberPresence : MemberReference | ||||
{ | { | ||||
public sealed class GameInfo | |||||
public class GameInfo | |||||
{ | { | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
@@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
{ | { | ||||
public class Message : MessageReference | public class Message : MessageReference | ||||
{ | { | ||||
public sealed class Attachment | |||||
public class Attachment | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public string Id { get; set; } | public string Id { get; set; } | ||||
@@ -18,14 +18,14 @@ namespace Discord.API.Client | |||||
[JsonProperty("filename")] | [JsonProperty("filename")] | ||||
public string Filename { get; set; } | public string Filename { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public int Width { get; set; } | |||||
public int? Width { get; set; } | |||||
[JsonProperty("height")] | [JsonProperty("height")] | ||||
public int Height { get; set; } | |||||
public int? Height { get; set; } | |||||
} | } | ||||
public sealed class Embed | |||||
public class Embed | |||||
{ | { | ||||
public sealed class Reference | |||||
public class Reference | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
@@ -33,25 +33,25 @@ namespace Discord.API.Client | |||||
public string Name { get; set; } | public string Name { get; set; } | ||||
} | } | ||||
public sealed class ThumbnailInfo | |||||
public class ThumbnailInfo | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
[JsonProperty("proxy_url")] | [JsonProperty("proxy_url")] | ||||
public string ProxyUrl { get; set; } | public string ProxyUrl { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public int Width { get; set; } | |||||
public int? Width { get; set; } | |||||
[JsonProperty("height")] | [JsonProperty("height")] | ||||
public int Height { get; set; } | |||||
public int? Height { get; set; } | |||||
} | } | ||||
public sealed class VideoInfo | |||||
public class VideoInfo | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public int Width { get; set; } | |||||
public int? Width { get; set; } | |||||
[JsonProperty("height")] | [JsonProperty("height")] | ||||
public int Height { get; set; } | |||||
public int? Height { get; set; } | |||||
} | } | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class HeartbeatCommand : IWebSocketMessage | |||||
public class HeartbeatCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; | int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; | ||||
object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); | object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); | ||||
@@ -4,7 +4,7 @@ using System.Collections.Generic; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class IdentifyCommand : IWebSocketMessage | |||||
public class IdentifyCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class RequestMembersCommand : IWebSocketMessage | |||||
public class RequestMembersCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class ResumeCommand : IWebSocketMessage | |||||
public class ResumeCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.Resume; | int IWebSocketMessage.OpCode => (int)OpCodes.Resume; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -3,13 +3,13 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class UpdateStatusCommand : IWebSocketMessage | |||||
public class UpdateStatusCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; | int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
bool IWebSocketMessage.IsPrivate => false; | bool IWebSocketMessage.IsPrivate => false; | ||||
public sealed class GameInfo | |||||
public class GameInfo | |||||
{ | { | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class UpdateVoiceCommand : IWebSocketMessage | |||||
public class UpdateVoiceCommand : IWebSocketMessage | |||||
{ | { | ||||
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; | int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; | ||||
object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class ChannelCreateEvent : Channel { } | |||||
public class ChannelCreateEvent : Channel { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class ChannelDeleteEvent : Channel { } | |||||
public class ChannelDeleteEvent : Channel { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class ChannelUpdateEvent : Channel { } | |||||
public class ChannelUpdateEvent : Channel { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildBanAddEvent : MemberReference { } | |||||
public class GuildBanAddEvent : MemberReference { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildBanRemoveEvent : MemberReference { } | |||||
public class GuildBanRemoveEvent : MemberReference { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildCreateEvent : ExtendedGuild { } | |||||
public class GuildCreateEvent : ExtendedGuild { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildDeleteEvent : ExtendedGuild { } | |||||
public class GuildDeleteEvent : ExtendedGuild { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket.Events | namespace Discord.API.Client.GatewaySocket.Events | ||||
{ | { | ||||
//public sealed class GuildEmojisUpdateEvent { } | |||||
//public class GuildEmojisUpdateEvent { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
//public sealed class GuildIntegrationsUpdateEvent { } | |||||
//public class GuildIntegrationsUpdateEvent { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildMemberAddEvent : Member { } | |||||
public class GuildMemberAddEvent : Member { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildMemberRemoveEvent : Member { } | |||||
public class GuildMemberRemoveEvent : Member { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildMemberUpdateEvent : Member { } | |||||
public class GuildMemberUpdateEvent : Member { } | |||||
} | } |
@@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildMembersChunkEvent | |||||
public class GuildMembersChunkEvent | |||||
{ | { | ||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
@@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildRoleCreateEvent | |||||
public class GuildRoleCreateEvent | |||||
{ | { | ||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildRoleDeleteEvent : RoleReference { } | |||||
public class GuildRoleDeleteEvent : RoleReference { } | |||||
} | } |
@@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildRoleUpdateEvent | |||||
public class GuildRoleUpdateEvent | |||||
{ | { | ||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class GuildUpdateEvent : Guild { } | |||||
public class GuildUpdateEvent : Guild { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class MessageAckEvent : MessageReference { } | |||||
public class MessageAckEvent : MessageReference { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class MessageCreateEvent : Message { } | |||||
public class MessageCreateEvent : Message { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class MessageDeleteEvent : MessageReference { } | |||||
public class MessageDeleteEvent : MessageReference { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class MessageUpdateEvent : Message { } | |||||
public class MessageUpdateEvent : Message { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class PresenceUpdateEvent : MemberPresence { } | |||||
public class PresenceUpdateEvent : MemberPresence { } | |||||
} | } |
@@ -2,9 +2,9 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class ReadyEvent | |||||
public class ReadyEvent | |||||
{ | { | ||||
public sealed class ReadState | |||||
public class ReadState | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public string ChannelId { get; set; } | public string ChannelId { get; set; } | ||||
@@ -2,7 +2,7 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class RedirectEvent | |||||
public class RedirectEvent | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
@@ -2,7 +2,7 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class ResumedEvent | |||||
public class ResumedEvent | |||||
{ | { | ||||
[JsonProperty("heartbeat_interval")] | [JsonProperty("heartbeat_interval")] | ||||
public int HeartbeatInterval { get; set; } | public int HeartbeatInterval { get; set; } | ||||
@@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class TypingStartEvent | |||||
public class TypingStartEvent | |||||
{ | { | ||||
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong UserId { get; set; } | public ulong UserId { get; set; } | ||||
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
//public sealed class UserSettingsUpdateEvent { } | |||||
//public class UserSettingsUpdateEvent { } | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class UserUpdateEvent : User { } | |||||
public class UserUpdateEvent : User { } | |||||
} | } |
@@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class VoiceServerUpdateEvent | |||||
public class VoiceServerUpdateEvent | |||||
{ | { | ||||
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
@@ -1,4 +1,4 @@ | |||||
namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
{ | { | ||||
public sealed class VoiceStateUpdateEvent : MemberVoiceState { } | |||||
public class VoiceStateUpdateEvent : MemberVoiceState { } | |||||
} | } |
@@ -0,0 +1,9 @@ | |||||
using System.IO; | |||||
namespace Discord.API.Client | |||||
{ | |||||
public interface ISerializable | |||||
{ | |||||
void Write(BinaryWriter writer); | |||||
} | |||||
} |
@@ -8,7 +8,7 @@ namespace Discord.API.Client | |||||
object Payload { get; } | object Payload { get; } | ||||
bool IsPrivate { get; } | bool IsPrivate { get; } | ||||
} | } | ||||
public sealed class WebSocketMessage | |||||
public class WebSocketMessage | |||||
{ | { | ||||
[JsonProperty("op")] | [JsonProperty("op")] | ||||
public int? Operation { get; set; } | public int? Operation { get; set; } | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class AcceptInviteRequest : IRestRequest<InviteReference> | |||||
public class AcceptInviteRequest : IRestRequest<InviteReference> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"invite/{InviteId}"; | string IRestRequest.Endpoint => $"invite/{InviteId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class AckMessageRequest : IRestRequest | |||||
public class AckMessageRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; | ||||
@@ -4,10 +4,10 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class AddChannelPermissionsRequest : IRestRequest | |||||
public class AddChannelPermissionsRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "PUT"; | string IRestRequest.Method => "PUT"; | ||||
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions"; | |||||
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; | |||||
object IRestRequest.Payload => this; | object IRestRequest.Payload => this; | ||||
bool IRestRequest.IsPrivate => false; | bool IRestRequest.IsPrivate => false; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class AddGuildBanRequest : IRestRequest | |||||
public class AddGuildBanRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "PUT"; | string IRestRequest.Method => "PUT"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class CreateChannelRequest : IRestRequest<Channel> | |||||
public class CreateChannelRequest : IRestRequest<Channel> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class CreateGuildRequest : IRestRequest<Guild> | |||||
public class CreateGuildRequest : IRestRequest<Guild> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"guilds"; | string IRestRequest.Endpoint => $"guilds"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class CreateInviteRequest : IRestRequest<Invite> | |||||
public class CreateInviteRequest : IRestRequest<Invite> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; | ||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class CreatePrivateChannelRequest : IRestRequest<Channel> | |||||
public class CreatePrivateChannelRequest : IRestRequest<Channel> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"users/@me/channels"; | string IRestRequest.Endpoint => $"users/@me/channels"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class CreateRoleRequest : IRestRequest<Role> | |||||
public class CreateRoleRequest : IRestRequest<Role> | |||||
{ | { | ||||
string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class DeleteChannelRequest : IRestRequest<Channel> | |||||
public class DeleteChannelRequest : IRestRequest<Channel> | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"channels/{ChannelId}"; | string IRestRequest.Endpoint => $"channels/{ChannelId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class DeleteInviteRequest : IRestRequest<Invite> | |||||
public class DeleteInviteRequest : IRestRequest<Invite> | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"invite/{InviteCode}"; | string IRestRequest.Endpoint => $"invite/{InviteCode}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class DeleteMessageRequest : IRestRequest | |||||
public class DeleteMessageRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class DeleteRoleRequest : IRestRequest | |||||
public class DeleteRoleRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GatewayRequest : IRestRequest<GatewayResponse> | |||||
public class GatewayRequest : IRestRequest<GatewayResponse> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"gateway"; | string IRestRequest.Endpoint => $"gateway"; | ||||
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest | |||||
bool IRestRequest.IsPrivate => false; | bool IRestRequest.IsPrivate => false; | ||||
} | } | ||||
public sealed class GatewayResponse | |||||
public class GatewayResponse | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetBansRequest : IRestRequest<UserReference[]> | |||||
public class GetBansRequest : IRestRequest<UserReference[]> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetInviteRequest : IRestRequest<InviteReference> | |||||
public class GetInviteRequest : IRestRequest<InviteReference> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"invite/{InviteCode}"; | string IRestRequest.Endpoint => $"invite/{InviteCode}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetInvitesRequest : IRestRequest<InviteReference[]> | |||||
public class GetInvitesRequest : IRestRequest<InviteReference[]> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; | ||||
@@ -4,7 +4,7 @@ using System.Text; | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetMessagesRequest : IRestRequest<Message[]> | |||||
public class GetMessagesRequest : IRestRequest<Message[]> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint | string IRestRequest.Endpoint | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]> | |||||
public class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"voice/regions"; | string IRestRequest.Endpoint => $"voice/regions"; | ||||
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest | |||||
bool IRestRequest.IsPrivate => false; | bool IRestRequest.IsPrivate => false; | ||||
} | } | ||||
public sealed class GetVoiceRegionsResponse | |||||
public class GetVoiceRegionsResponse | |||||
{ | { | ||||
[JsonProperty("sample_hostname")] | [JsonProperty("sample_hostname")] | ||||
public string Hostname { get; set; } | public string Hostname { get; set; } | ||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class GetWidgetRequest : IRestRequest<GetWidgetResponse> | |||||
public class GetWidgetRequest : IRestRequest<GetWidgetResponse> | |||||
{ | { | ||||
string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; | string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; | ||||
@@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest | |||||
} | } | ||||
} | } | ||||
public sealed class GetWidgetResponse | |||||
public class GetWidgetResponse | |||||
{ | { | ||||
public sealed class Channel | |||||
public class Channel | |||||
{ | { | ||||
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | ||||
public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
@@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest | |||||
[JsonProperty("position")] | [JsonProperty("position")] | ||||
public int Position { get; set; } | public int Position { get; set; } | ||||
} | } | ||||
public sealed class User : UserReference | |||||
public class User : UserReference | |||||
{ | { | ||||
[JsonProperty("avatar_url")] | [JsonProperty("avatar_url")] | ||||
public string AvatarUrl { get; set; } | public string AvatarUrl { get; set; } | ||||
@@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest | |||||
[JsonProperty("game")] | [JsonProperty("game")] | ||||
public UserGame Game { get; set; } | public UserGame Game { get; set; } | ||||
} | } | ||||
public sealed class UserGame | |||||
public class UserGame | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public int Id { get; set; } | public int Id { get; set; } | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class KickMemberRequest : IRestRequest | |||||
public class KickMemberRequest : IRestRequest | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class LeaveGuildRequest : IRestRequest<Guild> | |||||
public class LeaveGuildRequest : IRestRequest<Guild> | |||||
{ | { | ||||
string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
string IRestRequest.Endpoint => $"guilds/{GuildId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}"; | ||||
@@ -3,7 +3,7 @@ | |||||
namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
{ | { | ||||
[JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
public sealed class LoginRequest : IRestRequest<LoginResponse> | |||||
public class LoginRequest : IRestRequest<LoginResponse> | |||||
{ | { | ||||
string IRestRequest.Method => Email != null ? "POST" : "GET"; | string IRestRequest.Method => Email != null ? "POST" : "GET"; | ||||
string IRestRequest.Endpoint => $"auth/login"; | string IRestRequest.Endpoint => $"auth/login"; | ||||
@@ -16,7 +16,7 @@ namespace Discord.API.Client.Rest | |||||
public string Password { get; set; } | public string Password { get; set; } | ||||
} | } | ||||
public sealed class LoginResponse | |||||
public class LoginResponse | |||||
{ | { | ||||
[JsonProperty("token")] | [JsonProperty("token")] | ||||
public string Token { get; set; } | public string Token { get; set; } | ||||