diff --git a/docs/features/commands.rst b/docs/features/commands.rst
index 31da73c65..8abfb18a9 100644
--- a/docs/features/commands.rst
+++ b/docs/features/commands.rst
@@ -1,5 +1,5 @@
-Commands
-========
+|stub| Commands
+===============
The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands.
@@ -11,10 +11,10 @@ Example (Simple)
.. literalinclude:: /samples/command.cs
:language: csharp6
:tab-width: 2
-
+
Example (Groups)
----------------
.. literalinclude:: /samples/command_group.cs
:language: csharp6
- :tab-width: 2
\ No newline at end of file
+ :tab-width: 2
diff --git a/docs/features/events.rst b/docs/features/events.rst
index 862d28dd3..c3ab1e3b9 100644
--- a/docs/features/events.rst
+++ b/docs/features/events.rst
@@ -1,75 +1,75 @@
-|stub| Events
-=============
+Events
+======
Usage
-----
-To take advantage of Events in Discord.Net, you need to hook into them.
-
-There are two ways of hooking into events. See the example for examples on using these events.
-
-Usable Events
--------------
-+--------------------+--------------------+------------------------------------------+
-| Event Name | EventArgs | Description |
-+====================+====================+==========================================+
-| UserBanned | BanEventArgs | Called when a user is banned. |
-+--------------------+--------------------+------------------------------------------+
-| UserUnbanned | BanEventArgs | Called when a user is unbanned. |
-+--------------------+--------------------+------------------------------------------+
-| ChannelCreated | ChannelEventArgs | Called when a channel is created. |
-+--------------------+--------------------+------------------------------------------+
-| ChannelDestroyed | ChannelEventArgs | Called when a channel is destroyed. |
-+--------------------+--------------------+------------------------------------------+
-| ChannelUpdated | ChannelEventArgs | Called when a channel is updated. |
-+--------------------+--------------------+------------------------------------------+
-| MessageReceived | MessageEventArgs | Called when a message is received. |
-+--------------------+--------------------+------------------------------------------+
-| MessageSent | MessageEventArgs | Called when a message is sent. |
-+--------------------+--------------------+------------------------------------------+
-| MessageDeleted | MessageEventArgs | Called when a message is deleted. |
-+--------------------+--------------------+------------------------------------------+
-| MessageUpdated | MessageEventArgs | Called when a message is updated\\edited.|
-+--------------------+--------------------+------------------------------------------+
-| MessageReadRemotely| MessageEventArgs | Called when a message is read. |
-+--------------------+--------------------+------------------------------------------+
-| RoleCreated | RoleEventArgs | Called when a role is created. |
-+--------------------+--------------------+------------------------------------------+
-| RoleUpdated | RoleEventArgs | Called when a role is updated. |
-+--------------------+--------------------+------------------------------------------+
-| RoleDeleted | RoleEventArgs | Called when a role is deleted. |
-+--------------------+--------------------+------------------------------------------+
-| JoinedServer | ServerEventArgs | Called when a member joins a server. |
-+--------------------+--------------------+------------------------------------------+
-| LeftServer | ServerEventArgs | Called when a member leaves a server. |
-+--------------------+--------------------+------------------------------------------+
-| ServerUpdated | ServerEventArgs | Called when a server is updated. |
-+--------------------+--------------------+------------------------------------------+
-| ServerUnavailable | ServerEventArgs | Called when a Discord server goes down. |
-+--------------------+--------------------+------------------------------------------+
-| ServerAvailable | ServerEventArgs |Called when a Discord server goes back up.|
-+--------------------+--------------------+------------------------------------------+
-| UserJoined | UserEventArgs | Called when a user joins a Channel. |
-+--------------------+--------------------+------------------------------------------+
-| UserLeft | UserEventArgs | Called when a user leaves a Channel. |
-+--------------------+--------------------+------------------------------------------+
-| UserUpdated | UserEventArgs | --- |
-+--------------------+--------------------+------------------------------------------+
-| UserPresenceUpdated| UserEventArgs | Called when a user's presence changes. |
-| | | (Here\\Away) |
-+--------------------+--------------------+------------------------------------------+
-| UserVoiceState | UserEventArgs | Called when a user's voice state changes.|
-| Updated | | (Muted\\Unmuted) |
-+--------------------+--------------------+------------------------------------------+
-|UserIsTypingUpdated | UserEventArgs | Called when a user starts\\stops typing. |
-+--------------------+--------------------+------------------------------------------+
-| UserIsSpeaking | UserEventArgs | Called when a user's voice state changes.|
-| Updated | | (Speaking\\Not Speaking) |
-+--------------------+--------------------+------------------------------------------+
-| ProfileUpdated | N/A | Called when a user's profile changes. |
-+--------------------+--------------------+------------------------------------------+
-Example
--------
-
-.. literalinclude:: /samples/events.cs
- :language: csharp6
- :tab-width: 2
\ No newline at end of file
+Events in Discord.NET are raised using the Event system in c#. Most events are raised on the ``DiscordClient`` class.
+
+Most events in Discord.NET explain theirselves by their name.
+
+Messages
+--------
+
+The Four Message Events (MessageReceived, Updated, Deleted, and Acknowledged) are raised when a message has been modified/created.
+
+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 += (s, e) => {
+ // Check to make sure that the bot is not the author
+ if (!e.Message.IsAuthor)
+ // Echo the message back to the channel
+ e.Channel.SendMessage(e.Message);
+ };
+
+Users
+-----
+
+There are Six 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. (caused by a 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 += (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.
+ logChannel.SendMessage($"User Banned: {e.User.Name}")
+ };
+
+ // Register a Hook into the UserUpdated event using a Lambda
+ _client.UserUpdated += (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;
+
+ // do something...
+ };
+
+Connection States
+-----------------
+
+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.
diff --git a/docs/features/permissions.rst b/docs/features/permissions.rst
index b4922cb53..058fe07cf 100644
--- a/docs/features/permissions.rst
+++ b/docs/features/permissions.rst
@@ -1,11 +1,11 @@
Permissions
-==================
+===========
There are two types of permissions: *Channel Permissions* and *Server Permissions*.
Channel Permissions
-------------------
-Channel Permissions have a set of bools behind them:
+Channel Permissions are controlled using a set of flags:
======================= ======= ==============
Flag Type Description
@@ -49,7 +49,7 @@ Otherwise, you can use a single DualChannelPermissions.
Server Permissions
------------------
-Server permisisons are read-only, you cannot change them. You may still access them, however, using User.GetServerPermissions();
+Server Permissions can be accessed by ``Server.GetPermissions(User)``, and updated with ``Server.UpdatePermissions(User, ServerPermissions)``
A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions:
@@ -57,7 +57,7 @@ A user's server permissions also contain the default values for it's channel per
Flag Type Description
======================= ======= ==============
BanMembers Server Ban users from the server.
-KickMembers Server Kick users from the server. They can stil rejoin.
+KickMembers Server Kick users from the server. They can still rejoin.
ManageRoles Server Manage roles on the server, and their permissions.
ManageChannels Server Manage channels that exist on the server (add, remove them)
ManageServer Server Manage the server settings.
@@ -69,7 +69,7 @@ Managing permissions for roles is much easier than for users in channels. For ro
Example
-------
-
+
.. literalinclude:: /samples/permissions.cs
:language: csharp6
- :tab-width: 2
\ No newline at end of file
+ :tab-width: 2
diff --git a/docs/index.rst b/docs/index.rst
index c90a2936b..c4469cd46 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,7 +1,7 @@
Discord.Net
===========
-Discord.Net is an unofficial C# wrapper around the `Discord chat service`.
+Discord.Net is an unofficial C# wrapper around the `Discord Chat Service`.
It offers several methods to create automated operations, bots, or even custom clients.
Feel free to join us in the `Discord API chat`_.
@@ -9,28 +9,28 @@ Feel free to join us in the `Discord API chat`_.
.. _Discord chat service: https://discordapp.com
.. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx
-Warning
--------
+.. warn::
-This is an alpha!
+This is a beta!
-This library has been built thanks to a community effort reverse engineering the Discord client.
-As Discord is still in alpha, it may change at any time without notice, breaking this library as well.
-Discord.Net itself is also in early development and you will often encounter breaking changes until the official Discord API is released.
+This library has been built thanks to a community effort reverse engineering the Discord client.
+As the API is still unofficial, it may change at any time without notice, breaking this library as well.
+Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released.
It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_.
.. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx
+This Documentation is **currently undergoing a rewrite**. Some pages (marked with a wrench) are not updated, or are not completed yet.
+
.. toctree::
:caption: Documentation
:maxdepth: 2
-
- getting_started
- features/logging
+
+ getting_started
+ features/logging
features/management
features/permissions
- features/profile
features/commands
features/voice
- features/events
\ No newline at end of file
+ features/events
diff --git a/docs/samples/getting_started.cs b/docs/samples/getting_started.cs
index 228ef234e..55f7923a4 100644
--- a/docs/samples/getting_started.cs
+++ b/docs/samples/getting_started.cs
@@ -11,17 +11,17 @@ class Program
client.MessageReceived += async (s, e) =>
{
if (!e.Message.IsAuthor)
- await client.SendMessage(e.Channel, e.Message.Text);
+ await e.Channel.SendMessage(e.Message.Text);
};
//Convert our sync method to an async one and block the Main function until the bot disconnects
- client.Run(async () =>
+ client.ExecuteAndWait(async () =>
{
//Connect to the Discord server using our email and password
await client.Connect("discordtest@email.com", "Password123");
//If we are not a member of any server, use our invite code (made beforehand in the official Discord Client)
- if (!client.AllServers.Any())
+ if (!client.Servers.Any())
await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee"));
});
}
diff --git a/docs/samples/permissions.cs b/docs/samples/permissions.cs
index c09a151d6..419026714 100644
--- a/docs/samples/permissions.cs
+++ b/docs/samples/permissions.cs
@@ -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);
-}
\ No newline at end of file
diff --git a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj b/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
index 280dc619e..99cfdcc0b 100644
--- a/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
+++ b/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
@@ -62,8 +62,8 @@
InternalIsSpeakingEventArgs.cs
-
- Net\VoiceWebSocket.cs
+
+ Net\VoiceSocket.cs
Opus\OpusConverter.cs
@@ -74,15 +74,15 @@
Opus\OpusEncoder.cs
-
- SimpleAudioClient.cs
-
Sodium\SecretBox.cs
UserIsTalkingEventArgs.cs
+
+ VirtualClient.cs
+
VoiceBuffer.cs
diff --git a/src/Discord.Net.Audio/AudioClient.cs b/src/Discord.Net.Audio/AudioClient.cs
index 9657b89a8..279557029 100644
--- a/src/Discord.Net.Audio/AudioClient.cs
+++ b/src/Discord.Net.Audio/AudioClient.cs
@@ -1,9 +1,12 @@
using Discord.API.Client.GatewaySocket;
+using Discord.API.Client.Rest;
using Discord.Logging;
+using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Newtonsoft.Json;
using Nito.AsyncEx;
using System;
+using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -41,124 +44,175 @@ namespace Discord.Audio
}
}
+ private readonly DiscordConfig _config;
private readonly AsyncLock _connectionLock;
- private readonly JsonSerializer _serializer;
- private CancellationTokenSource _cancelTokenSource;
+ private readonly TaskManager _taskManager;
+ private ConnectionState _gatewayState;
- internal AudioService Service { get; }
internal Logger Logger { get; }
+
public int Id { get; }
+ public AudioService Service { get; }
+ public AudioServiceConfig Config { get; }
+ public RestClient ClientAPI { get; }
public GatewaySocket GatewaySocket { get; }
- public VoiceWebSocket VoiceSocket { get; }
+ public VoiceSocket VoiceSocket { get; }
+ public JsonSerializer Serializer { get; }
public Stream OutputStream { get; }
-
+
+ public CancellationToken CancelToken { get; private set; }
+ public string SessionId { get; private set; }
+
public ConnectionState State => VoiceSocket.State;
public Server Server => VoiceSocket.Server;
public Channel Channel => VoiceSocket.Channel;
- public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger)
+ public AudioClient(DiscordClient client, Server server, int id)
{
- Service = service;
- _serializer = service.Client.Serializer;
- Id = clientId;
- GatewaySocket = gatewaySocket;
- Logger = logger;
- OutputStream = new OutStream(this);
+ Id = id;
+ _config = client.Config;
+ Service = client.Audio();
+ Config = Service.Config;
+ Serializer = client.Serializer;
+ _gatewayState = (int)ConnectionState.Disconnected;
- _connectionLock = new AsyncLock();
+ //Logging
+ Logger = client.Log.CreateLogger($"AudioClient #{id}");
- GatewaySocket.ReceivedDispatch += OnReceivedDispatch;
+ //Async
+ _taskManager = new TaskManager(Cleanup, false);
+ _connectionLock = new AsyncLock();
+ CancelToken = new CancellationToken(true);
- VoiceSocket = new VoiceWebSocket(service.Client, this, logger);
+ //Networking
+ if (Config.EnableMultiserver)
+ {
+ ClientAPI = new RestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}"));
+ GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}"));
+ GatewaySocket.Connected += (s, e) =>
+ {
+ if (_gatewayState == ConnectionState.Connecting)
+ EndGatewayConnect();
+ };
+ }
+ else
+ GatewaySocket = client.GatewaySocket;
+ GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
+ VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}"));
VoiceSocket.Server = server;
-
- /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
- _voiceSocket.Disconnected += async (s, e) =>
- {
- _voiceSocket.CurrentServerId;
- if (voiceServerId != null)
- _gatewaySocket.SendLeaveVoice(voiceServerId.Value);
- await _voiceSocket.Disconnect().ConfigureAwait(false);
- RaiseVoiceDisconnected(socket.CurrentServerId.Value, e);
- if (e.WasUnexpected)
- await socket.Reconnect().ConfigureAwait(false);
- };*/
-
- /*_voiceSocket.IsSpeaking += (s, e) =>
- {
- if (_voiceSocket.State == WebSocketState.Connected)
- {
- var user = _users[e.UserId, socket.CurrentServerId];
- bool value = e.IsSpeaking;
- if (user.IsSpeaking != value)
- {
- user.IsSpeaking = value;
- var channel = _channels[_voiceSocket.CurrentChannelId];
- RaiseUserIsSpeaking(user, channel, value);
- if (Config.TrackActivity)
- user.UpdateActivity();
- }
- }
- };*/
-
- /*this.Connected += (s, e) =>
- {
- _voiceSocket.ParentCancelToken = _cancelToken;
- };*/
+ OutputStream = new OutStream(this);
}
- public async Task Join(Channel channel)
- {
- if (channel == null) throw new ArgumentNullException(nameof(channel));
- if (channel.Type != ChannelType.Voice)
- throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
- if (channel.Server != VoiceSocket.Server)
- throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
- if (channel == VoiceSocket.Channel) return;
- if (VoiceSocket.Server == null)
- throw new InvalidOperationException("This client has been closed.");
-
- using (await _connectionLock.LockAsync().ConfigureAwait(false))
+ public async Task Connect()
+ {
+ if (Config.EnableMultiserver)
+ await BeginGatewayConnect().ConfigureAwait(false);
+ else
{
- VoiceSocket.Channel = channel;
-
- await Task.Run(() =>
+ var cancelSource = new CancellationTokenSource();
+ CancelToken = cancelSource.Token;
+ await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
+ }
+ }
+ private async Task BeginGatewayConnect()
+ {
+ try
+ {
+ using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
- SendVoiceUpdate();
- VoiceSocket.WaitForConnection(_cancelTokenSource.Token);
- });
+ await Disconnect().ConfigureAwait(false);
+ _taskManager.ClearException();
+
+ ClientAPI.Token = Service.Client.ClientAPI.Token;
+
+ Stopwatch stopwatch = null;
+ if (_config.LogLevel >= LogSeverity.Verbose)
+ stopwatch = Stopwatch.StartNew();
+ _gatewayState = ConnectionState.Connecting;
+
+ var cancelSource = new CancellationTokenSource();
+ CancelToken = cancelSource.Token;
+ ClientAPI.CancelToken = CancelToken;
+
+ await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false);
+
+ await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
+ GatewaySocket.WaitForConnection(CancelToken);
+
+ if (_config.LogLevel >= LogSeverity.Verbose)
+ {
+ stopwatch.Stop();
+ double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2);
+ Logger.Verbose($"Connection took {seconds} sec");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await _taskManager.SignalError(ex).ConfigureAwait(false);
+ throw;
}
}
+ private void EndGatewayConnect()
+ {
+ _gatewayState = ConnectionState.Connected;
+ }
- public async Task Connect(bool connectGateway)
+ public async Task Disconnect()
{
- using (await _connectionLock.LockAsync().ConfigureAwait(false))
- {
- _cancelTokenSource = new CancellationTokenSource();
- var cancelToken = _cancelTokenSource.Token;
- VoiceSocket.ParentCancelToken = cancelToken;
+ await _taskManager.Stop(true).ConfigureAwait(false);
+ if (Config.EnableMultiserver)
+ ClientAPI.Token = null;
+ }
+ private async Task Cleanup()
+ {
+ var oldState = _gatewayState;
+ _gatewayState = ConnectionState.Disconnecting;
- if (connectGateway)
+ if (Config.EnableMultiserver)
+ {
+ if (oldState == ConnectionState.Connected)
{
- GatewaySocket.ParentCancelToken = cancelToken;
- await GatewaySocket.Connect().ConfigureAwait(false);
- GatewaySocket.WaitForConnection(cancelToken);
+ try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); }
+ catch (OperationCanceledException) { }
}
+
+ await GatewaySocket.Disconnect().ConfigureAwait(false);
+ ClientAPI.Token = null;
}
+
+ var server = VoiceSocket.Server;
+ VoiceSocket.Server = null;
+ VoiceSocket.Channel = null;
+ if (Config.EnableMultiserver)
+ await Service.RemoveClient(server, this).ConfigureAwait(false);
+ SendVoiceUpdate(server.Id, null);
+
+ await VoiceSocket.Disconnect().ConfigureAwait(false);
+ if (Config.EnableMultiserver)
+ await GatewaySocket.Disconnect().ConfigureAwait(false);
+
+ _gatewayState = (int)ConnectionState.Disconnected;
}
- public async Task Disconnect()
+ public async Task Join(Channel channel)
{
+ if (channel == null) throw new ArgumentNullException(nameof(channel));
+ if (channel.Type != ChannelType.Voice)
+ throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
+ if (channel == VoiceSocket.Channel) return;
+ var server = channel.Server;
+ if (server != VoiceSocket.Server)
+ throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
+ if (VoiceSocket.Server == null)
+ throw new InvalidOperationException("This client has been closed.");
+
+ SendVoiceUpdate(channel.Server.Id, channel.Id);
using (await _connectionLock.LockAsync().ConfigureAwait(false))
- {
- await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false);
- VoiceSocket.Channel = null;
- SendVoiceUpdate();
- await VoiceSocket.Disconnect();
- }
+ await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken));
}
- private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
+ private async void OnReceivedEvent(WebSocketEventEventArgs e)
{
try
{
@@ -166,11 +220,11 @@ namespace Discord.Audio
{
case "VOICE_STATE_UPDATE":
{
- var data = e.Payload.ToObject(_serializer);
+ var data = e.Payload.ToObject(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{
if (data.ChannelId == null)
- await Disconnect();
+ await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value);
@@ -179,7 +233,7 @@ namespace Discord.Audio
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
- await Disconnect();
+ await Disconnect().ConfigureAwait(false);
}
}
}
@@ -187,13 +241,16 @@ namespace Discord.Audio
break;
case "VOICE_SERVER_UPDATE":
{
- var data = e.Payload.ToObject(_serializer);
+ var data = e.Payload.ToObject(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
var client = Service.Client;
- VoiceSocket.Token = data.Token;
- VoiceSocket.Host = "wss://" + e.Payload.Value("endpoint").Split(':')[0];
- await VoiceSocket.Connect().ConfigureAwait(false);
+ var id = client.CurrentUser?.Id;
+ if (id != null)
+ {
+ var host = "wss://" + e.Payload.Value("endpoint").Split(':')[0];
+ await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false);
+ }
}
}
break;
@@ -205,9 +262,6 @@ namespace Discord.Audio
}
}
- /// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer.
- /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames.
- /// Number of bytes in this frame.
public void Send(byte[] data, int offset, int count)
{
if (data == null) throw new ArgumentException(nameof(data));
@@ -219,29 +273,22 @@ namespace Discord.Audio
VoiceSocket.SendPCMFrames(data, offset, count);
}
- /// Clears the PCM buffer.
public void Clear()
{
if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.ClearPCMFrames();
}
-
- /// Returns a task that completes once the voice output buffer is empty.
public void Wait()
{
if (VoiceSocket.Server == null) return; //Has been closed
VoiceSocket.WaitForQueue();
}
- private void SendVoiceUpdate()
+ public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{
- var serverId = VoiceSocket.Server?.Id;
- if (serverId != null)
- {
- GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id,
- (Service.Config.Mode | AudioMode.Outgoing) == 0,
- (Service.Config.Mode | AudioMode.Incoming) == 0);
- }
+ GatewaySocket.SendUpdateVoice(serverId, channelId,
+ (Service.Config.Mode | AudioMode.Outgoing) == 0,
+ (Service.Config.Mode | AudioMode.Incoming) == 0);
}
}
}
diff --git a/src/Discord.Net.Audio/AudioClient.cs.old b/src/Discord.Net.Audio/AudioClient.cs.old
new file mode 100644
index 000000000..17ee171b0
--- /dev/null
+++ b/src/Discord.Net.Audio/AudioClient.cs.old
@@ -0,0 +1,242 @@
+using Discord.API.Client.GatewaySocket;
+using Discord.Logging;
+using Discord.Net.Rest;
+using Discord.Net.WebSockets;
+using Newtonsoft.Json;
+using Nito.AsyncEx;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Discord.Audio
+{
+ internal class AudioClient : IAudioClient
+ {
+ private class OutStream : Stream
+ {
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+
+ private readonly AudioClient _client;
+
+ internal OutStream(AudioClient client)
+ {
+ _client = client;
+ }
+
+ public override long Length { get { throw new InvalidOperationException(); } }
+ public override long Position
+ {
+ get { throw new InvalidOperationException(); }
+ set { throw new InvalidOperationException(); }
+ }
+ public override void Flush() { throw new InvalidOperationException(); }
+ public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); }
+ public override void SetLength(long value) { throw new InvalidOperationException(); }
+ public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); }
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _client.Send(buffer, offset, count);
+ }
+ }
+
+ private readonly JsonSerializer _serializer;
+ private readonly bool _ownsGateway;
+ private TaskManager _taskManager;
+ private CancellationToken _cancelToken;
+
+ internal AudioService Service { get; }
+ internal Logger Logger { get; }
+ public int Id { get; }
+ public GatewaySocket GatewaySocket { get; }
+ public VoiceSocket VoiceSocket { get; }
+ public Stream OutputStream { get; }
+
+ public ConnectionState State => VoiceSocket.State;
+ public Server Server => VoiceSocket.Server;
+ public Channel Channel => VoiceSocket.Channel;
+
+ public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, bool ownsGateway, Logger logger)
+ {
+ Service = service;
+ _serializer = service.Client.Serializer;
+ Id = clientId;
+ GatewaySocket = gatewaySocket;
+ _ownsGateway = ownsGateway;
+ Logger = logger;
+ OutputStream = new OutStream(this);
+ _taskManager = new TaskManager(Cleanup, true);
+
+ GatewaySocket.ReceivedDispatch += OnReceivedDispatch;
+
+ VoiceSocket = new VoiceSocket(service.Client.Config, service.Config, service.Client.Serializer, logger);
+ VoiceSocket.Server = server;
+
+ /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
+ _voiceSocket.Disconnected += async (s, e) =>
+ {
+ _voiceSocket.CurrentServerId;
+ if (voiceServerId != null)
+ _gatewaySocket.SendLeaveVoice(voiceServerId.Value);
+ await _voiceSocket.Disconnect().ConfigureAwait(false);
+ RaiseVoiceDisconnected(socket.CurrentServerId.Value, e);
+ if (e.WasUnexpected)
+ await socket.Reconnect().ConfigureAwait(false);
+ };*/
+
+ /*_voiceSocket.IsSpeaking += (s, e) =>
+ {
+ if (_voiceSocket.State == WebSocketState.Connected)
+ {
+ var user = _users[e.UserId, socket.CurrentServerId];
+ bool value = e.IsSpeaking;
+ if (user.IsSpeaking != value)
+ {
+ user.IsSpeaking = value;
+ var channel = _channels[_voiceSocket.CurrentChannelId];
+ RaiseUserIsSpeaking(user, channel, value);
+ if (Config.TrackActivity)
+ user.UpdateActivity();
+ }
+ }
+ };*/
+
+ /*this.Connected += (s, e) =>
+ {
+ _voiceSocket.ParentCancelToken = _cancelToken;
+ };*/
+ }
+
+ public async Task Join(Channel channel)
+ {
+ if (channel == null) throw new ArgumentNullException(nameof(channel));
+ if (channel.Type != ChannelType.Voice)
+ throw new ArgumentException("Channel must be a voice channel.", nameof(channel));
+ if (channel.Server != VoiceSocket.Server)
+ throw new ArgumentException("This is channel is not part of the current server.", nameof(channel));
+ if (channel == VoiceSocket.Channel) return;
+ if (VoiceSocket.Server == null)
+ throw new InvalidOperationException("This client has been closed.");
+
+ SendVoiceUpdate(channel.Server.Id, channel.Id);
+ await Task.Run(() => VoiceSocket.WaitForConnection(_cancelToken));
+ }
+
+ public async Task Connect(RestClient rest = null)
+ {
+ var cancelSource = new CancellationTokenSource();
+ _cancelToken = cancelSource.Token;
+
+ Task[] tasks;
+ if (rest != null)
+ tasks = new Task[] { GatewaySocket.Connect(rest, _cancelToken) };
+ else
+ tasks = new Task[0];
+
+ await _taskManager.Start(tasks, cancelSource);
+ }
+
+ public Task Disconnect() => _taskManager.Stop(true);
+
+ private async Task Cleanup()
+ {
+ var server = VoiceSocket.Server;
+ VoiceSocket.Server = null;
+ VoiceSocket.Channel = null;
+
+ await Service.RemoveClient(server, this).ConfigureAwait(false);
+ SendVoiceUpdate(server.Id, null);
+
+ await VoiceSocket.Disconnect().ConfigureAwait(false);
+ if (_ownsGateway)
+ await GatewaySocket.Disconnect().ConfigureAwait(false);
+ }
+
+ private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
+ {
+ try
+ {
+ switch (e.Type)
+ {
+ case "VOICE_STATE_UPDATE":
+ {
+ var data = e.Payload.ToObject(_serializer);
+ if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
+ {
+ if (data.ChannelId == null)
+ await Disconnect().ConfigureAwait(false);
+ else
+ {
+ var channel = Service.Client.GetChannel(data.ChannelId.Value);
+ if (channel != null)
+ VoiceSocket.Channel = channel;
+ else
+ {
+ Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
+ await Disconnect().ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ break;
+ case "VOICE_SERVER_UPDATE":
+ {
+ var data = e.Payload.ToObject(_serializer);
+ if (data.GuildId == VoiceSocket.Server?.Id)
+ {
+ var client = Service.Client;
+ var id = client.CurrentUser?.Id;
+ if (id != null)
+ {
+ var host = "wss://" + e.Payload.Value("endpoint").Split(':')[0];
+ await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, _cancelToken).ConfigureAwait(false);
+ }
+ }
+ }
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"Error handling {e.Type} event", ex);
+ }
+ }
+
+ /// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer.
+ /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames.
+ /// Number of bytes in this frame.
+ public void Send(byte[] data, int offset, int count)
+ {
+ if (data == null) throw new ArgumentException(nameof(data));
+ if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
+ if (offset < 0) throw new ArgumentOutOfRangeException(nameof(count));
+ if (VoiceSocket.Server == null) return; //Has been closed
+ if (count == 0) return;
+
+ VoiceSocket.SendPCMFrames(data, offset, count);
+ }
+
+ /// Clears the PCM buffer.
+ public void Clear()
+ {
+ if (VoiceSocket.Server == null) return; //Has been closed
+ VoiceSocket.ClearPCMFrames();
+ }
+
+ /// Returns a task that completes once the voice output buffer is empty.
+ public void Wait()
+ {
+ if (VoiceSocket.Server == null) return; //Has been closed
+ VoiceSocket.WaitForQueue();
+ }
+
+ public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
+ {
+ GatewaySocket.SendUpdateVoice(serverId, channelId,
+ (Service.Config.Mode | AudioMode.Outgoing) == 0,
+ (Service.Config.Mode | AudioMode.Incoming) == 0);
+ }
+ }
+}
diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs
index ec134f884..b64bf53f8 100644
--- a/src/Discord.Net.Audio/AudioService.cs
+++ b/src/Discord.Net.Audio/AudioService.cs
@@ -1,4 +1,4 @@
-using Discord.Net.WebSockets;
+using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Linq;
@@ -8,8 +8,10 @@ namespace Discord.Audio
{
public class AudioService : IService
{
- private AudioClient _defaultClient;
- private ConcurrentDictionary _voiceClients;
+ private readonly AsyncLock _asyncLock;
+ private AudioClient _defaultClient; //Only used for single server
+ private VirtualClient _currentClient; //Only used for single server
+ private ConcurrentDictionary _voiceClients;
private ConcurrentDictionary _talkingUsers;
private int _nextClientId;
@@ -30,18 +32,20 @@ namespace Discord.Audio
public AudioService(AudioServiceConfig config)
{
Config = config;
- }
+ _asyncLock = new AsyncLock();
+
+ }
void IService.Install(DiscordClient client)
{
Client = client;
Config.Lock();
if (Config.EnableMultiserver)
- _voiceClients = new ConcurrentDictionary();
+ _voiceClients = new ConcurrentDictionary();
else
{
var logger = Client.Log.CreateLogger("Voice");
- _defaultClient = new SimpleAudioClient(this, 0, logger);
+ _defaultClient = new AudioClient(Client, null, 0);
}
_talkingUsers = new ConcurrentDictionary();
@@ -75,68 +79,30 @@ namespace Discord.Audio
{
if (server == null) throw new ArgumentNullException(nameof(server));
- if (!Config.EnableMultiserver)
+ if (Config.EnableMultiserver)
{
- if (server == _defaultClient.Server)
- return (_defaultClient as SimpleAudioClient).CurrentClient;
+ AudioClient client;
+ if (_voiceClients.TryGetValue(server.Id, out client))
+ return client;
else
return null;
}
else
{
- IAudioClient client;
- if (_voiceClients.TryGetValue(server.Id, out client))
- return client;
+ if (server == _currentClient.Server)
+ return _currentClient;
else
return null;
}
}
- private async Task CreateClient(Server server)
- {
- var client = _voiceClients.GetOrAdd(server.Id, _ => null); //Placeholder, so we can't have two clients connecting at once
-
- if (client == null)
- {
- int id = unchecked(++_nextClientId);
-
- var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}");
- var voiceLogger = Client.Log.CreateLogger($"Voice #{id}");
- var gatewaySocket = new GatewaySocket(Client, gatewayLogger);
- var voiceClient = new AudioClient(this, id, server, Client.GatewaySocket, voiceLogger);
-
- await voiceClient.Connect(true).ConfigureAwait(false);
-
- /*voiceClient.VoiceSocket.FrameReceived += (s, e) =>
- {
- OnFrameReceieved(e);
- };
- voiceClient.VoiceSocket.UserIsSpeaking += (s, e) =>
- {
- var user = server.GetUser(e.UserId);
- OnUserIsSpeakingUpdated(user, e.IsSpeaking);
- };*/
-
- //Update the placeholder only it still exists (RemoveClient wasnt called)
- if (!_voiceClients.TryUpdate(server.Id, voiceClient, null))
- {
- //If it was, cleanup
- await voiceClient.Disconnect().ConfigureAwait(false); ;
- await gatewaySocket.Disconnect().ConfigureAwait(false); ;
- }
- }
- return client;
- }
-
- //TODO: This isn't threadsafe
- internal async Task RemoveClient(Server server, IAudioClient client)
+
+ //Called from AudioClient.Disconnect
+ internal async Task RemoveClient(Server server, AudioClient client)
{
- if (Config.EnableMultiserver && server != null)
+ using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
- if (_voiceClients.TryRemove(server.Id, out client))
- {
- await client.Disconnect();
- await (client as AudioClient).GatewaySocket.Disconnect();
- }
+ if (_voiceClients.TryUpdate(server.Id, null, client))
+ _voiceClients.TryRemove(server.Id, out client);
}
}
@@ -144,16 +110,48 @@ namespace Discord.Audio
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
- if (!Config.EnableMultiserver)
- {
- await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false);
- return _defaultClient;
- }
- else
+ var server = channel.Server;
+ using (await _asyncLock.LockAsync().ConfigureAwait(false))
{
- var client = await CreateClient(channel.Server).ConfigureAwait(false);
- await client.Join(channel).ConfigureAwait(false);
- return client;
+ if (Config.EnableMultiserver)
+ {
+ AudioClient client;
+ if (!_voiceClients.TryGetValue(server.Id, out client))
+ {
+ client = new AudioClient(Client, server, unchecked(++_nextClientId));
+ _voiceClients[server.Id] = client;
+
+ await client.Connect().ConfigureAwait(false);
+
+ /*voiceClient.VoiceSocket.FrameReceived += (s, e) =>
+ {
+ OnFrameReceieved(e);
+ };
+ voiceClient.VoiceSocket.UserIsSpeaking += (s, e) =>
+ {
+ var user = server.GetUser(e.UserId);
+ OnUserIsSpeakingUpdated(user, e.IsSpeaking);
+ };*/
+ }
+
+ await client.Join(channel).ConfigureAwait(false);
+ return client;
+ }
+ else
+ {
+ if (_defaultClient.Server != server)
+ {
+ await _defaultClient.Disconnect();
+ _defaultClient.VoiceSocket.Server = server;
+ await _defaultClient.Connect().ConfigureAwait(false);
+ }
+ var client = new VirtualClient(_defaultClient, server);
+ _currentClient = client;
+
+ await client.Join(channel).ConfigureAwait(false);
+ return client;
+ }
+
}
}
@@ -163,15 +161,18 @@ namespace Discord.Audio
if (Config.EnableMultiserver)
{
- IAudioClient client;
+ AudioClient client;
if (_voiceClients.TryRemove(server.Id, out client))
await client.Disconnect().ConfigureAwait(false);
}
else
{
- IAudioClient client = GetClient(server);
- if (client != null)
- await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false);
+ using (await _asyncLock.LockAsync().ConfigureAwait(false))
+ {
+ var client = GetClient(server) as VirtualClient;
+ if (client != null)
+ await _defaultClient.Disconnect().ConfigureAwait(false);
+ }
}
}
}
diff --git a/src/Discord.Net.Audio/AudioServiceConfig.cs b/src/Discord.Net.Audio/AudioServiceConfig.cs
index b476a6467..a9b4a4b33 100644
--- a/src/Discord.Net.Audio/AudioServiceConfig.cs
+++ b/src/Discord.Net.Audio/AudioServiceConfig.cs
@@ -12,6 +12,8 @@ namespace Discord.Audio
public class AudioServiceConfig
{
+ public const int MaxBitrate = 128;
+
/// Max time in milliseconds to wait for DiscordAudioClient to connect and initialize.
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } }
private int _connectionTimeout = 30000;
@@ -33,8 +35,8 @@ namespace Discord.Audio
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } }
private int _bufferLength = 1000;
- /// 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.
- public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
+ /// 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.
+ public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
private int? _bitrate = null;
/// 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).
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } }
diff --git a/src/Discord.Net.Audio/IAudioClient.cs b/src/Discord.Net.Audio/IAudioClient.cs
index 838ec945a..71c8783d5 100644
--- a/src/Discord.Net.Audio/IAudioClient.cs
+++ b/src/Discord.Net.Audio/IAudioClient.cs
@@ -1,16 +1,38 @@
-using System.IO;
+using Discord.Net.Rest;
+using Discord.Net.WebSockets;
+using System.IO;
+using System.Threading;
using System.Threading.Tasks;
namespace Discord.Audio
{
public interface IAudioClient
{
+ /// Gets the unique identifier for this client.
+ int Id { get; }
+ /// Gets the session id for the current connection.
+ string SessionId { get; }
+ /// Gets the current state of this client.
ConnectionState State { get; }
+ /// Gets the channel this client is currently a member of.
Channel Channel { get; }
+ /// Gets the server this client is bound to.
Server Server { get; }
+ /// Gets a stream object that wraps the Send() function.
Stream OutputStream { get; }
+ /// Gets a cancellation token that triggers when the client is manually disconnected.
+ CancellationToken CancelToken { get; }
+ /// Gets the internal RestClient for the Client API endpoint.
+ RestClient ClientAPI { get; }
+ /// Gets the internal WebSocket for the Gateway event stream.
+ GatewaySocket GatewaySocket { get; }
+ /// Gets the internal WebSocket for the Voice control stream.
+ VoiceSocket VoiceSocket { get; }
+
+ /// Moves the client to another channel on the same server.
Task Join(Channel channel);
+ /// Disconnects from the Discord server, canceling any pending requests.
Task Disconnect();
/// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer.
diff --git a/src/Discord.Net.Audio/Net/VoiceWebSocket.cs b/src/Discord.Net.Audio/Net/VoiceSocket.cs
similarity index 90%
rename from src/Discord.Net.Audio/Net/VoiceWebSocket.cs
rename to src/Discord.Net.Audio/Net/VoiceSocket.cs
index c7c4ee0e2..bab928b53 100644
--- a/src/Discord.Net.Audio/Net/VoiceWebSocket.cs
+++ b/src/Discord.Net.Audio/Net/VoiceSocket.cs
@@ -19,7 +19,7 @@ using System.Threading.Tasks;
namespace Discord.Net.WebSockets
{
- public partial class VoiceWebSocket : WebSocket
+ public partial class VoiceSocket : WebSocket
{
private const int MaxOpusSize = 4000;
private const string EncryptedMode = "xsalsa20_poly1305";
@@ -27,8 +27,7 @@ namespace Discord.Net.WebSockets
private readonly int _targetAudioBufferLength;
private readonly ConcurrentDictionary _decoders;
- private readonly AudioClient _audioClient;
- private readonly AudioServiceConfig _config;
+ private readonly AudioServiceConfig _audioConfig;
private Task _sendTask, _receiveTask;
private VoiceBuffer _sendBuffer;
private OpusEncoder _encoder;
@@ -41,6 +40,8 @@ namespace Discord.Net.WebSockets
private ushort _sequence;
private string _encryptionMode;
private int _ping;
+ private ulong? _userId;
+ private string _sessionId;
public string Token { get; internal set; }
public Server Server { get; internal set; }
@@ -57,32 +58,37 @@ namespace Discord.Net.WebSockets
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count)
=> FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count));
- internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, Logger logger)
- : base(client, logger)
+ internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger)
+ : base(config, serializer, logger)
{
- _audioClient = audioClient;
- _config = client.Audio().Config;
+ _audioConfig = audioConfig;
_decoders = new ConcurrentDictionary();
- _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames
+ _targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize];
_ssrcMapping = new ConcurrentDictionary();
- _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed);
- _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
+ _encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed);
+ _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
}
- public Task Connect()
- => BeginConnect();
+ public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken)
+ {
+ Host = host;
+ Token = token;
+ _userId = userId;
+ _sessionId = sessionId;
+ return BeginConnect(parentCancelToken);
+ }
private async Task Reconnect()
{
try
{
- var cancelToken = ParentCancelToken.Value;
- await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
+ var cancelToken = _parentCancelToken;
+ await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
try
{
- await Connect().ConfigureAwait(false);
+ await BeginConnect(_parentCancelToken).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
@@ -90,31 +96,35 @@ namespace Discord.Net.WebSockets
{
Logger.Error("Reconnect failed", ex);
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
- await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
+ await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException) { }
}
- public Task Disconnect() => _taskManager.Stop(true);
+ public async Task Disconnect()
+ {
+ await _taskManager.Stop(true).ConfigureAwait(false);
+ _userId = null;
+ }
protected override async Task Run()
{
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
List tasks = new List();
- if (_config.Mode.HasFlag(AudioMode.Outgoing))
+ if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing))
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken));
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken));
- SendIdentify();
+ SendIdentify(_userId.Value, _sessionId);
#if !DOTNET5_4
tasks.Add(WatcherAsync());
#endif
tasks.AddRange(_engine.GetTasks(CancelToken));
tasks.Add(HeartbeatAsync(CancelToken));
- await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
+ await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false);
}
protected override async Task Cleanup()
{
@@ -148,7 +158,7 @@ namespace Discord.Net.WebSockets
int packetLength, resultOffset, resultLength;
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
- if ((_config.Mode & AudioMode.Incoming) != 0)
+ if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
{
decodingBuffer = new byte[MaxOpusSize];
nonce = new byte[24];
@@ -184,7 +194,7 @@ namespace Discord.Net.WebSockets
int port = packet[68] | packet[69] << 8;
SendSelectProtocol(ip, port);
- if ((_config.Mode & AudioMode.Incoming) == 0)
+ if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
return; //We dont need this thread anymore
}
else
@@ -395,7 +405,7 @@ namespace Discord.Net.WebSockets
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
_endpoint = new IPEndPoint(address, payload.Port);
- if (_config.EnableEncryption)
+ if (_audioConfig.EnableEncryption)
{
if (payload.Modes.Contains(EncryptedMode))
{
@@ -467,12 +477,12 @@ namespace Discord.Net.WebSockets
public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand());
- public void SendIdentify()
+ public void SendIdentify(ulong id, string sessionId)
=> QueueMessage(new IdentifyCommand
{
GuildId = Server.Id,
- UserId = _client.CurrentUser.Id,
- SessionId = _client.SessionId,
+ UserId = id,
+ SessionId = sessionId,
Token = Token
});
public void SendSelectProtocol(string externalAddress, int externalPort)
diff --git a/src/Discord.Net.Audio/Opus/OpusConverter.cs b/src/Discord.Net.Audio/Opus/OpusConverter.cs
index 2a5a4f567..25bda033e 100644
--- a/src/Discord.Net.Audio/Opus/OpusConverter.cs
+++ b/src/Discord.Net.Audio/Opus/OpusConverter.cs
@@ -4,13 +4,13 @@ using System.Security;
namespace Discord.Audio.Opus
{
- public enum OpusApplication : int
+ internal enum OpusApplication : int
{
Voice = 2048,
MusicOrMixed = 2049,
LowLatency = 2051
}
- public enum OpusError : int
+ internal enum OpusError : int
{
OK = 0,
BadArg = -1,
@@ -22,7 +22,7 @@ namespace Discord.Audio.Opus
AllocFail = -7
}
- public abstract class OpusConverter : IDisposable
+ internal abstract class OpusConverter : IDisposable
{
protected enum Ctl : int
{
diff --git a/src/Discord.Net.Audio/Opus/OpusDecoder.cs b/src/Discord.Net.Audio/Opus/OpusDecoder.cs
index 9077ea9cf..d8e6b8087 100644
--- a/src/Discord.Net.Audio/Opus/OpusDecoder.cs
+++ b/src/Discord.Net.Audio/Opus/OpusDecoder.cs
@@ -2,7 +2,6 @@
namespace Discord.Audio.Opus
{
- /// Opus codec wrapper.
internal class OpusDecoder : OpusConverter
{
/// Creates a new Opus decoder.
diff --git a/src/Discord.Net.Audio/Opus/OpusEncoder.cs b/src/Discord.Net.Audio/Opus/OpusEncoder.cs
index 258faf481..be0623c6b 100644
--- a/src/Discord.Net.Audio/Opus/OpusEncoder.cs
+++ b/src/Discord.Net.Audio/Opus/OpusEncoder.cs
@@ -2,7 +2,6 @@
namespace Discord.Audio.Opus
{
- /// Opus codec wrapper.
internal class OpusEncoder : OpusConverter
{
/// Gets the bit rate in kbit/s.
@@ -19,7 +18,7 @@ namespace Discord.Audio.Opus
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application)
: base(samplingRate, channels, frameLength)
{
- if (bitrate != null && (bitrate < 1 || bitrate > 512))
+ if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate))
throw new ArgumentOutOfRangeException(nameof(bitrate));
BitRate = bitrate;
diff --git a/src/Discord.Net.Audio/SimpleAudioClient.cs b/src/Discord.Net.Audio/SimpleAudioClient.cs
deleted file mode 100644
index b073e2ed3..000000000
--- a/src/Discord.Net.Audio/SimpleAudioClient.cs
+++ /dev/null
@@ -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 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;
- }
- }
- }
-}
diff --git a/src/Discord.Net.Audio/VirtualClient.cs b/src/Discord.Net.Audio/VirtualClient.cs
new file mode 100644
index 000000000..12d285a0d
--- /dev/null
+++ b/src/Discord.Net.Audio/VirtualClient.cs
@@ -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();
+ }
+}
diff --git a/src/Discord.Net.Audio/project.json b/src/Discord.Net.Audio/project.json
index be5a82244..c460f0e6c 100644
--- a/src/Discord.Net.Audio/project.json
+++ b/src/Discord.Net.Audio/project.json
@@ -13,8 +13,8 @@
"contentFiles": [ "libsodium.dll", "opus.dll" ],
"compilationOptions": {
- "warningsAsErrors": true,
- "allowUnsafe": true
+ "allowUnsafe": true,
+ "warningsAsErrors": true
},
"dependencies": {
diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs
index 8aec0b90f..a8addc1b1 100644
--- a/src/Discord.Net.Commands/Command.cs
+++ b/src/Discord.Net.Commands/Command.cs
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
namespace Discord.Commands
{
- public sealed class Command
+ //TODO: Make this more friendly and expose it to be extendable
+ public class Command
{
private string[] _aliases;
internal CommandParameter[] _parameters;
diff --git a/src/Discord.Net.Commands/CommandBuilder.cs b/src/Discord.Net.Commands/CommandBuilder.cs
index 6b29cd893..129dc24ab 100644
--- a/src/Discord.Net.Commands/CommandBuilder.cs
+++ b/src/Discord.Net.Commands/CommandBuilder.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
namespace Discord.Commands
{
+ //TODO: Make this more friendly and expose it to be extendable
public sealed class CommandBuilder
{
private readonly CommandService _service;
@@ -18,17 +19,20 @@ namespace Discord.Commands
public CommandService Service => _service;
- internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable initialChecks = null)
+ internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable initialChecks = null)
{
- _service = service;
- _command = command;
- _command.Category = category;
- _params = new List();
+ _service = service;
+ _prefix = prefix;
+
+ _command = new Command(AppendPrefix(prefix, text));
+ _command.Category = category;
+
if (initialChecks != null)
_checks = new List(initialChecks);
else
_checks = new List();
- _prefix = prefix;
+
+ _params = new List();
_aliases = new List();
_allowRequiredParams = true;
@@ -112,7 +116,7 @@ namespace Discord.Commands
return prefix;
}
}
- public sealed class CommandGroupBuilder
+ public class CommandGroupBuilder
{
private readonly CommandService _service;
private readonly string _prefix;
@@ -121,10 +125,11 @@ namespace Discord.Commands
public CommandService Service => _service;
- internal CommandGroupBuilder(CommandService service, string prefix, IEnumerable initialChecks = null)
+ internal CommandGroupBuilder(CommandService service, string prefix = "", string category = null, IEnumerable initialChecks = null)
{
_service = service;
_prefix = prefix;
+ _category = category;
if (initialChecks != null)
_checks = new List(initialChecks);
else
@@ -145,17 +150,14 @@ namespace Discord.Commands
_checks.Add(new GenericPermissionChecker(checkFunc, errorMsg));
}
- public CommandGroupBuilder CreateGroup(string cmd, Action config = null)
- {
- config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _checks));
+ public CommandGroupBuilder CreateGroup(string cmd, Action config)
+ {
+ config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _category, _checks));
return this;
}
public CommandBuilder CreateCommand()
=> CreateCommand("");
public CommandBuilder CreateCommand(string cmd)
- {
- var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd));
- return new CommandBuilder(_service, command, _prefix, _category, _checks);
- }
+ => new CommandBuilder(_service, cmd, _prefix, _category, _checks);
}
}
diff --git a/src/Discord.Net.Commands/CommandMap.cs b/src/Discord.Net.Commands/CommandMap.cs
index 6dd9d2ff1..98decd833 100644
--- a/src/Discord.Net.Commands/CommandMap.cs
+++ b/src/Discord.Net.Commands/CommandMap.cs
@@ -20,16 +20,20 @@ namespace Discord.Commands
public IEnumerable Commands => _commands;
public IEnumerable SubGroups => _items.Values;
- public CommandMap(CommandMap parent, string name, string fullName)
+ public CommandMap()
+ {
+ _items = new Dictionary();
+ _commands = new List();
+ _isVisible = false;
+ _hasNonAliases = false;
+ _hasSubGroups = false;
+ }
+ public CommandMap(CommandMap parent, string name, string fullName)
+ : this()
{
_parent = parent;
_name = name;
_fullName = fullName;
- _items = new Dictionary();
- _commands = new List();
- _isVisible = false;
- _hasNonAliases = false;
- _hasSubGroups = false;
}
public CommandMap GetItem(string text)
diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/CommandParameter.cs
index 413674b78..d7361bef4 100644
--- a/src/Discord.Net.Commands/CommandParameter.cs
+++ b/src/Discord.Net.Commands/CommandParameter.cs
@@ -11,13 +11,13 @@
/// Catches all remaining text as a single optional parameter.
Unparsed
}
- public sealed class CommandParameter
+ public class CommandParameter
{
public string Name { get; }
public int Id { get; internal set; }
public ParameterType Type { get; }
- public CommandParameter(string name, ParameterType type)
+ internal CommandParameter(string name, ParameterType type)
{
Name = name;
Type = type;
diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs
index d87b61aff..8d5e691bf 100644
--- a/src/Discord.Net.Commands/CommandService.cs
+++ b/src/Discord.Net.Commands/CommandService.cs
@@ -34,9 +34,9 @@ namespace Discord.Commands
Config = config;
_allCommands = new List();
- _map = new CommandMap(null, "", "");
+ _map = new CommandMap();
_categories = new Dictionary();
- Root = new CommandGroupBuilder(this, "", null);
+ Root = new CommandGroupBuilder(this);
}
void IService.Install(DiscordClient client)
@@ -309,7 +309,7 @@ namespace Discord.Commands
string categoryName = command.Category ?? "";
if (!_categories.TryGetValue(categoryName, out category))
{
- category = new CommandMap(null, "", "");
+ category = new CommandMap();
_categories.Add(categoryName, category);
}
diff --git a/src/Discord.Net.Modules/ModuleManager.cs b/src/Discord.Net.Modules/ModuleManager.cs
index 091529229..17b2b166b 100644
--- a/src/Discord.Net.Modules/ModuleManager.cs
+++ b/src/Discord.Net.Modules/ModuleManager.cs
@@ -7,7 +7,7 @@ using System.Linq;
namespace Discord.Modules
{
- public sealed class ModuleManager
+ public class ModuleManager
{
public event EventHandler ServerEnabled = delegate { };
public event EventHandler ServerDisabled = delegate { };
@@ -15,31 +15,31 @@ namespace Discord.Modules
public event EventHandler ChannelDisabled = delegate { };
public event EventHandler LeftServer = delegate { };
- public event EventHandler ServerUpdated = delegate { };
+ public event EventHandler ServerUpdated = delegate { };
public event EventHandler ServerUnavailable = delegate { };
public event EventHandler ServerAvailable = delegate { };
public event EventHandler ChannelCreated = delegate { };
public event EventHandler ChannelDestroyed = delegate { };
- public event EventHandler ChannelUpdated = delegate { };
+ public event EventHandler ChannelUpdated = delegate { };
public event EventHandler RoleCreated = delegate { };
- public event EventHandler RoleUpdated = delegate { };
+ public event EventHandler RoleUpdated = delegate { };
public event EventHandler RoleDeleted = delegate { };
public event EventHandler UserBanned = delegate { };
public event EventHandler UserJoined = delegate { };
public event EventHandler UserLeft = delegate { };
- public event EventHandler UserUpdated = delegate { };
- public event EventHandler UserPresenceUpdated = delegate { };
- public event EventHandler UserVoiceStateUpdated = delegate { };
+ public event EventHandler UserUpdated = delegate { };
+ //public event EventHandler UserPresenceUpdated = delegate { };
+ //public event EventHandler UserVoiceStateUpdated = delegate { };
public event EventHandler UserUnbanned = delegate { };
- public event EventHandler UserIsTypingUpdated = delegate { };
+ public event EventHandler UserIsTyping = delegate { };
public event EventHandler MessageReceived = delegate { };
public event EventHandler MessageSent = delegate { };
public event EventHandler MessageDeleted = delegate { };
- public event EventHandler MessageUpdated = delegate { };
+ public event EventHandler MessageUpdated = delegate { };
public event EventHandler MessageReadRemotely = delegate { };
private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate;
@@ -79,11 +79,12 @@ namespace Discord.Modules
if (_allowAll || _useServerWhitelist) //Server-only events
{
client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); };
- client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); };
+ //TODO: This *is* a channel update if the before/after voice channel is whitelisted
+ //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); };
}
client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); };
- client.ChannelUpdated += (s, e) => { if (HasChannel(e.Channel)) ChannelUpdated(s, e); };
+ client.ChannelUpdated += (s, e) => { if (HasChannel(e.After)) ChannelUpdated(s, e); };
client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); };
client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); };
@@ -96,16 +97,16 @@ namespace Discord.Modules
client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); };
client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) { DisableServer(e.Server); LeftServer(s, e); } };
- client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.Server)) ServerUpdated(s, e); };
+ client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.After)) ServerUpdated(s, e); };
client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); };
client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); };
client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); };
client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); };
client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); };
- client.UserIsTypingUpdated += (s, e) => { if (HasChannel(e.Channel)) UserIsTypingUpdated(s, e); };
+ client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); };
//TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist
- client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); };
+ //client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); };
client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); };
client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); };
}
diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj
index 63bb83a09..0dce8fc1c 100644
--- a/src/Discord.Net.Net45/Discord.Net.csproj
+++ b/src/Discord.Net.Net45/Discord.Net.csproj
@@ -385,18 +385,9 @@
API\Status\Rest\UpcomingMaintenances.cs
-
- ChannelEventArgs.cs
-
-
- ChannelUserEventArgs.cs
-
Config.cs
-
- DisconnectedEventArgs.cs
-
DiscordClient.cs
@@ -406,6 +397,9 @@
DiscordConfig.cs
+
+ DynamicIL.cs
+
Enums\ChannelType.cs
@@ -418,12 +412,66 @@
Enums\PermissionTarget.cs
+
+ Enums\Relative.cs
+
Enums\StringEnum.cs
Enums\UserStatus.cs
+
+ ETF\ETFReader.cs
+
+
+ ETF\ETFType.cs
+
+
+ ETF\ETFWriter.cs
+
+
+ Events\ChannelEventArgs.cs
+
+
+ Events\ChannelUpdatedEventArgs.cs
+
+
+ Events\ChannelUserEventArgs.cs
+
+
+ Events\DisconnectedEventArgs.cs
+
+
+ Events\LogMessageEventArgs.cs
+
+
+ Events\MessageEventArgs.cs
+
+
+ Events\MessageUpdatedEventArgs.cs
+
+
+ Events\ProfileUpdatedEventArgs.cs
+
+
+ Events\RoleEventArgs.cs
+
+
+ Events\RoleUpdatedEventArgs.cs
+
+
+ Events\ServerEventArgs.cs
+
+
+ Events\ServerUpdatedEventArgs.cs
+
+
+ Events\UserEventArgs.cs
+
+
+ Events\UserUpdatedEventArgs.cs
+
Extensions.cs
@@ -445,12 +493,6 @@
Logging\LogManager.cs
-
- LogMessageEventArgs.cs
-
-
- MessageEventArgs.cs
-
MessageQueue.cs
@@ -532,27 +574,12 @@
Net\WebSockets\WS4NetEngine.cs
-
- ProfileEventArgs.cs
-
-
- RelativeDirection.cs
-
-
- RoleEventArgs.cs
-
-
- ServerEventArgs.cs
-
ServiceManager.cs
TaskManager.cs
-
- UserEventArgs.cs
-
diff --git a/src/Discord.Net/API/Client/Common/Channel.cs b/src/Discord.Net/API/Client/Common/Channel.cs
index f54c11e5c..90ed8bf38 100644
--- a/src/Discord.Net/API/Client/Common/Channel.cs
+++ b/src/Discord.Net/API/Client/Common/Channel.cs
@@ -5,7 +5,7 @@ namespace Discord.API.Client
{
public class Channel : ChannelReference
{
- public sealed class PermissionOverwrite
+ public class PermissionOverwrite
{
[JsonProperty("type")]
public string Type { get; set; }
diff --git a/src/Discord.Net/API/Client/Common/ExtendedGuild.cs b/src/Discord.Net/API/Client/Common/ExtendedGuild.cs
index 9da0355ca..bfb1971cc 100644
--- a/src/Discord.Net/API/Client/Common/ExtendedGuild.cs
+++ b/src/Discord.Net/API/Client/Common/ExtendedGuild.cs
@@ -4,7 +4,7 @@ namespace Discord.API.Client
{
public class ExtendedGuild : Guild
{
- public sealed class ExtendedMemberInfo : Member
+ public class ExtendedMemberInfo : Member
{
[JsonProperty("mute")]
public bool? IsServerMuted { get; set; }
diff --git a/src/Discord.Net/API/Client/Common/Guild.cs b/src/Discord.Net/API/Client/Common/Guild.cs
index bfa047c57..fd4519147 100644
--- a/src/Discord.Net/API/Client/Common/Guild.cs
+++ b/src/Discord.Net/API/Client/Common/Guild.cs
@@ -6,7 +6,7 @@ namespace Discord.API.Client
{
public class Guild : GuildReference
{
- public sealed class EmojiData
+ public class EmojiData
{
[JsonProperty("id")]
public string Id { get; set; }
diff --git a/src/Discord.Net/API/Client/Common/InviteReference.cs b/src/Discord.Net/API/Client/Common/InviteReference.cs
index 4c25d9ad3..194165173 100644
--- a/src/Discord.Net/API/Client/Common/InviteReference.cs
+++ b/src/Discord.Net/API/Client/Common/InviteReference.cs
@@ -4,7 +4,7 @@ namespace Discord.API.Client
{
public class InviteReference
{
- public sealed class GuildData : GuildReference
+ public class GuildData : GuildReference
{
[JsonProperty("splash_hash")]
public string Splash { get; set; }
diff --git a/src/Discord.Net/API/Client/Common/MemberPresence.cs b/src/Discord.Net/API/Client/Common/MemberPresence.cs
index 283b9a1d1..589ad46c1 100644
--- a/src/Discord.Net/API/Client/Common/MemberPresence.cs
+++ b/src/Discord.Net/API/Client/Common/MemberPresence.cs
@@ -5,7 +5,7 @@ namespace Discord.API.Client
{
public class MemberPresence : MemberReference
{
- public sealed class GameInfo
+ public class GameInfo
{
[JsonProperty("name")]
public string Name { get; set; }
diff --git a/src/Discord.Net/API/Client/Common/Message.cs b/src/Discord.Net/API/Client/Common/Message.cs
index f89d81d96..7e9271dc9 100644
--- a/src/Discord.Net/API/Client/Common/Message.cs
+++ b/src/Discord.Net/API/Client/Common/Message.cs
@@ -5,7 +5,7 @@ namespace Discord.API.Client
{
public class Message : MessageReference
{
- public sealed class Attachment
+ public class Attachment
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -18,14 +18,14 @@ namespace Discord.API.Client
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonProperty("width")]
- public int Width { get; set; }
+ public int? Width { get; set; }
[JsonProperty("height")]
- public int Height { get; set; }
+ public int? Height { get; set; }
}
- public sealed class Embed
+ public class Embed
{
- public sealed class Reference
+ public class Reference
{
[JsonProperty("url")]
public string Url { get; set; }
@@ -33,25 +33,25 @@ namespace Discord.API.Client
public string Name { get; set; }
}
- public sealed class ThumbnailInfo
+ public class ThumbnailInfo
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("proxy_url")]
public string ProxyUrl { get; set; }
[JsonProperty("width")]
- public int Width { get; set; }
+ public int? Width { get; set; }
[JsonProperty("height")]
- public int Height { get; set; }
+ public int? Height { get; set; }
}
- public sealed class VideoInfo
+ public class VideoInfo
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("width")]
- public int Width { get; set; }
+ public int? Width { get; set; }
[JsonProperty("height")]
- public int Height { get; set; }
+ public int? Height { get; set; }
}
[JsonProperty("url")]
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs
index 68a0e98e6..9f3f9cefb 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class HeartbeatCommand : IWebSocketMessage
+ public class HeartbeatCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat;
object IWebSocketMessage.Payload => EpochTime.GetMilliseconds();
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs
index 76a3a3e6f..2a56143c8 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class IdentifyCommand : IWebSocketMessage
+ public class IdentifyCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Identify;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs
index 2a2799c12..ea8ab4a75 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class RequestMembersCommand : IWebSocketMessage
+ public class RequestMembersCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs
index f473369cf..15486e577 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class ResumeCommand : IWebSocketMessage
+ public class ResumeCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Resume;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs
index 75bfce892..dff18b08c 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs
@@ -3,13 +3,13 @@
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateStatusCommand : IWebSocketMessage
+ public class UpdateStatusCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate;
object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false;
- public sealed class GameInfo
+ public class GameInfo
{
[JsonProperty("name")]
public string Name { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
index 4eced5dcf..3ccf92c65 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateVoiceCommand : IWebSocketMessage
+ public class UpdateVoiceCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs
index da4e47aa9..ca26fecc7 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class ChannelCreateEvent : Channel { }
+ public class ChannelCreateEvent : Channel { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs
index 64452dbe2..2b61a7d78 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class ChannelDeleteEvent : Channel { }
+ public class ChannelDeleteEvent : Channel { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs
index 908c65267..4565ce1bc 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class ChannelUpdateEvent : Channel { }
+ public class ChannelUpdateEvent : Channel { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs
index 823e3506a..7ba24473a 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildBanAddEvent : MemberReference { }
+ public class GuildBanAddEvent : MemberReference { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs
index a977b8702..a56a98494 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildBanRemoveEvent : MemberReference { }
+ public class GuildBanRemoveEvent : MemberReference { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs
index 13e3393fc..41c1c71c7 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildCreateEvent : ExtendedGuild { }
+ public class GuildCreateEvent : ExtendedGuild { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs
index 6850cc2b9..cf824c40e 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildDeleteEvent : ExtendedGuild { }
+ public class GuildDeleteEvent : ExtendedGuild { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs
index 8d2400a7b..06255bdcf 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket.Events
{
- //public sealed class GuildEmojisUpdateEvent { }
+ //public class GuildEmojisUpdateEvent { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs
index d5ac59521..0767b2f8f 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- //public sealed class GuildIntegrationsUpdateEvent { }
+ //public class GuildIntegrationsUpdateEvent { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs
index ab543a41b..4d1d7fed5 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildMemberAddEvent : Member { }
+ public class GuildMemberAddEvent : Member { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs
index cd76cd81a..311186b11 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildMemberRemoveEvent : Member { }
+ public class GuildMemberRemoveEvent : Member { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs
index 8e67d5e12..9b56a95b0 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildMemberUpdateEvent : Member { }
+ public class GuildMemberUpdateEvent : Member { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs
index 1cb256c79..23be74855 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildMembersChunkEvent
+ public class GuildMembersChunkEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs
index 28f999d5f..3d8e2f459 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildRoleCreateEvent
+ public class GuildRoleCreateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs
index 470420157..2ecd2edc5 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildRoleDeleteEvent : RoleReference { }
+ public class GuildRoleDeleteEvent : RoleReference { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs
index 291bab043..e26b65c4d 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildRoleUpdateEvent
+ public class GuildRoleUpdateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs
index ba1665f09..8fc0f1350 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class GuildUpdateEvent : Guild { }
+ public class GuildUpdateEvent : Guild { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs
index ed4048a62..64c106ef5 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class MessageAckEvent : MessageReference { }
+ public class MessageAckEvent : MessageReference { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs
index 9eb514b61..d6d2ec1cc 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class MessageCreateEvent : Message { }
+ public class MessageCreateEvent : Message { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs
index 9d160eb58..cfc2df7ff 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class MessageDeleteEvent : MessageReference { }
+ public class MessageDeleteEvent : MessageReference { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs
index 6fa852749..23521fd93 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class MessageUpdateEvent : Message { }
+ public class MessageUpdateEvent : Message { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs
index 4ecbccf05..c40853336 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class PresenceUpdateEvent : MemberPresence { }
+ public class PresenceUpdateEvent : MemberPresence { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs
index b17ffd61d..c672a30ae 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs
@@ -2,9 +2,9 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class ReadyEvent
+ public class ReadyEvent
{
- public sealed class ReadState
+ public class ReadState
{
[JsonProperty("id")]
public string ChannelId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs
index 26b2afd2d..fe9d644d4 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs
@@ -2,7 +2,7 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class RedirectEvent
+ public class RedirectEvent
{
[JsonProperty("url")]
public string Url { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs
index 7fd608b25..6a50fbe32 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs
@@ -2,7 +2,7 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class ResumedEvent
+ public class ResumedEvent
{
[JsonProperty("heartbeat_interval")]
public int HeartbeatInterval { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs
index ee8abebe6..484cec1bc 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
- public sealed class TypingStartEvent
+ public class TypingStartEvent
{
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
public ulong UserId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs
index 04effff5e..aad938157 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- //public sealed class UserSettingsUpdateEvent { }
+ //public class UserSettingsUpdateEvent { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs
index 6b43b34b6..3c366310a 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class UserUpdateEvent : User { }
+ public class UserUpdateEvent : User { }
}
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs
index 0725cc5d8..d305642a1 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket
{
- public sealed class VoiceServerUpdateEvent
+ public class VoiceServerUpdateEvent
{
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; }
diff --git a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs
index 448920b42..f3ba96b17 100644
--- a/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs
+++ b/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs
@@ -1,4 +1,4 @@
namespace Discord.API.Client.GatewaySocket
{
- public sealed class VoiceStateUpdateEvent : MemberVoiceState { }
+ public class VoiceStateUpdateEvent : MemberVoiceState { }
}
diff --git a/src/Discord.Net/API/Client/ISerializable.cs b/src/Discord.Net/API/Client/ISerializable.cs
new file mode 100644
index 000000000..d23dc3c6c
--- /dev/null
+++ b/src/Discord.Net/API/Client/ISerializable.cs
@@ -0,0 +1,9 @@
+using System.IO;
+
+namespace Discord.API.Client
+{
+ public interface ISerializable
+ {
+ void Write(BinaryWriter writer);
+ }
+}
diff --git a/src/Discord.Net/API/Client/IWebSocketMessage.cs b/src/Discord.Net/API/Client/IWebSocketMessage.cs
index 715110e51..6f6de535a 100644
--- a/src/Discord.Net/API/Client/IWebSocketMessage.cs
+++ b/src/Discord.Net/API/Client/IWebSocketMessage.cs
@@ -8,7 +8,7 @@ namespace Discord.API.Client
object Payload { get; }
bool IsPrivate { get; }
}
- public sealed class WebSocketMessage
+ public class WebSocketMessage
{
[JsonProperty("op")]
public int? Operation { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs
index 639a558ec..865e37c2d 100644
--- a/src/Discord.Net/API/Client/Rest/AcceptInvite.cs
+++ b/src/Discord.Net/API/Client/Rest/AcceptInvite.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class AcceptInviteRequest : IRestRequest
+ public class AcceptInviteRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"invite/{InviteId}";
diff --git a/src/Discord.Net/API/Client/Rest/AckMessage.cs b/src/Discord.Net/API/Client/Rest/AckMessage.cs
index 55b885e52..1678ed34b 100644
--- a/src/Discord.Net/API/Client/Rest/AckMessage.cs
+++ b/src/Discord.Net/API/Client/Rest/AckMessage.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class AckMessageRequest : IRestRequest
+ public class AckMessageRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack";
diff --git a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
index 7f878d609..c7a9f57ac 100644
--- a/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
+++ b/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
@@ -4,10 +4,10 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class AddChannelPermissionsRequest : IRestRequest
+ public class AddChannelPermissionsRequest : IRestRequest
{
string IRestRequest.Method => "PUT";
- string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions";
+ string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}";
object IRestRequest.Payload => this;
bool IRestRequest.IsPrivate => false;
diff --git a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs
index 295e39259..7699a74e4 100644
--- a/src/Discord.Net/API/Client/Rest/AddGuildBan.cs
+++ b/src/Discord.Net/API/Client/Rest/AddGuildBan.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class AddGuildBanRequest : IRestRequest
+ public class AddGuildBanRequest : IRestRequest
{
string IRestRequest.Method => "PUT";
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}";
diff --git a/src/Discord.Net/API/Client/Rest/CreateChannel.cs b/src/Discord.Net/API/Client/Rest/CreateChannel.cs
index 6a083e315..0dc45bc43 100644
--- a/src/Discord.Net/API/Client/Rest/CreateChannel.cs
+++ b/src/Discord.Net/API/Client/Rest/CreateChannel.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class CreateChannelRequest : IRestRequest
+ public class CreateChannelRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/channels";
diff --git a/src/Discord.Net/API/Client/Rest/CreateGuild.cs b/src/Discord.Net/API/Client/Rest/CreateGuild.cs
index e7b00934b..baa1f455e 100644
--- a/src/Discord.Net/API/Client/Rest/CreateGuild.cs
+++ b/src/Discord.Net/API/Client/Rest/CreateGuild.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class CreateGuildRequest : IRestRequest
+ public class CreateGuildRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds";
diff --git a/src/Discord.Net/API/Client/Rest/CreateInvite.cs b/src/Discord.Net/API/Client/Rest/CreateInvite.cs
index 2d9ae6a50..a55b9c7e9 100644
--- a/src/Discord.Net/API/Client/Rest/CreateInvite.cs
+++ b/src/Discord.Net/API/Client/Rest/CreateInvite.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class CreateInviteRequest : IRestRequest
+ public class CreateInviteRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/invites";
diff --git a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs
index 526267590..2d413a8d9 100644
--- a/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs
+++ b/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class CreatePrivateChannelRequest : IRestRequest
+ public class CreatePrivateChannelRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"users/@me/channels";
diff --git a/src/Discord.Net/API/Client/Rest/CreateRole.cs b/src/Discord.Net/API/Client/Rest/CreateRole.cs
index 8daa8145c..87715490d 100644
--- a/src/Discord.Net/API/Client/Rest/CreateRole.cs
+++ b/src/Discord.Net/API/Client/Rest/CreateRole.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class CreateRoleRequest : IRestRequest
+ public class CreateRoleRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles";
diff --git a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs
index 0a44d3b3c..6443c2387 100644
--- a/src/Discord.Net/API/Client/Rest/DeleteChannel.cs
+++ b/src/Discord.Net/API/Client/Rest/DeleteChannel.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class DeleteChannelRequest : IRestRequest
+ public class DeleteChannelRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"channels/{ChannelId}";
diff --git a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs
index b469caa00..5de8b348b 100644
--- a/src/Discord.Net/API/Client/Rest/DeleteInvite.cs
+++ b/src/Discord.Net/API/Client/Rest/DeleteInvite.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class DeleteInviteRequest : IRestRequest
+ public class DeleteInviteRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"invite/{InviteCode}";
diff --git a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs
index c209baa3a..33921cd1a 100644
--- a/src/Discord.Net/API/Client/Rest/DeleteMessage.cs
+++ b/src/Discord.Net/API/Client/Rest/DeleteMessage.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class DeleteMessageRequest : IRestRequest
+ public class DeleteMessageRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}";
diff --git a/src/Discord.Net/API/Client/Rest/DeleteRole.cs b/src/Discord.Net/API/Client/Rest/DeleteRole.cs
index 3ad327121..650ece9f2 100644
--- a/src/Discord.Net/API/Client/Rest/DeleteRole.cs
+++ b/src/Discord.Net/API/Client/Rest/DeleteRole.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class DeleteRoleRequest : IRestRequest
+ public class DeleteRoleRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}";
diff --git a/src/Discord.Net/API/Client/Rest/Gateway.cs b/src/Discord.Net/API/Client/Rest/Gateway.cs
index e728b46a0..ef9486ca1 100644
--- a/src/Discord.Net/API/Client/Rest/Gateway.cs
+++ b/src/Discord.Net/API/Client/Rest/Gateway.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GatewayRequest : IRestRequest
+ public class GatewayRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"gateway";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false;
}
- public sealed class GatewayResponse
+ public class GatewayResponse
{
[JsonProperty("url")]
public string Url { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/GetBans.cs b/src/Discord.Net/API/Client/Rest/GetBans.cs
index e4638ce32..ee07cb242 100644
--- a/src/Discord.Net/API/Client/Rest/GetBans.cs
+++ b/src/Discord.Net/API/Client/Rest/GetBans.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetBansRequest : IRestRequest
+ public class GetBansRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans";
diff --git a/src/Discord.Net/API/Client/Rest/GetInvite.cs b/src/Discord.Net/API/Client/Rest/GetInvite.cs
index 708a99e46..27de264f0 100644
--- a/src/Discord.Net/API/Client/Rest/GetInvite.cs
+++ b/src/Discord.Net/API/Client/Rest/GetInvite.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetInviteRequest : IRestRequest
+ public class GetInviteRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"invite/{InviteCode}";
diff --git a/src/Discord.Net/API/Client/Rest/GetInvites.cs b/src/Discord.Net/API/Client/Rest/GetInvites.cs
index dc056ad5a..079c54ef5 100644
--- a/src/Discord.Net/API/Client/Rest/GetInvites.cs
+++ b/src/Discord.Net/API/Client/Rest/GetInvites.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetInvitesRequest : IRestRequest
+ public class GetInvitesRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"guilds/{GuildId}/invites";
diff --git a/src/Discord.Net/API/Client/Rest/GetMessages.cs b/src/Discord.Net/API/Client/Rest/GetMessages.cs
index c5809ded1..b72b05c8b 100644
--- a/src/Discord.Net/API/Client/Rest/GetMessages.cs
+++ b/src/Discord.Net/API/Client/Rest/GetMessages.cs
@@ -4,7 +4,7 @@ using System.Text;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetMessagesRequest : IRestRequest
+ public class GetMessagesRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint
diff --git a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs
index 307fd01eb..7dc97ef31 100644
--- a/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs
+++ b/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetVoiceRegionsRequest : IRestRequest
+ public class GetVoiceRegionsRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"voice/regions";
@@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest
bool IRestRequest.IsPrivate => false;
}
- public sealed class GetVoiceRegionsResponse
+ public class GetVoiceRegionsResponse
{
[JsonProperty("sample_hostname")]
public string Hostname { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/GetWidget.cs b/src/Discord.Net/API/Client/Rest/GetWidget.cs
index bdb9b3a99..3b1006358 100644
--- a/src/Discord.Net/API/Client/Rest/GetWidget.cs
+++ b/src/Discord.Net/API/Client/Rest/GetWidget.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetWidgetRequest : IRestRequest
+ public class GetWidgetRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json";
@@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest
}
}
- public sealed class GetWidgetResponse
+ public class GetWidgetResponse
{
- public sealed class Channel
+ public class Channel
{
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; }
@@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("position")]
public int Position { get; set; }
}
- public sealed class User : UserReference
+ public class User : UserReference
{
[JsonProperty("avatar_url")]
public string AvatarUrl { get; set; }
@@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("game")]
public UserGame Game { get; set; }
}
- public sealed class UserGame
+ public class UserGame
{
[JsonProperty("id")]
public int Id { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/KickMember.cs b/src/Discord.Net/API/Client/Rest/KickMember.cs
index db69fbcd9..96804ff6b 100644
--- a/src/Discord.Net/API/Client/Rest/KickMember.cs
+++ b/src/Discord.Net/API/Client/Rest/KickMember.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class KickMemberRequest : IRestRequest
+ public class KickMemberRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}";
diff --git a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs
index 01dec28e0..6a8b3c0cf 100644
--- a/src/Discord.Net/API/Client/Rest/LeaveGuild.cs
+++ b/src/Discord.Net/API/Client/Rest/LeaveGuild.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class LeaveGuildRequest : IRestRequest
+ public class LeaveGuildRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}";
diff --git a/src/Discord.Net/API/Client/Rest/Login.cs b/src/Discord.Net/API/Client/Rest/Login.cs
index f4b0b0c92..ab7efc31b 100644
--- a/src/Discord.Net/API/Client/Rest/Login.cs
+++ b/src/Discord.Net/API/Client/Rest/Login.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class LoginRequest : IRestRequest
+ public class LoginRequest : IRestRequest
{
string IRestRequest.Method => Email != null ? "POST" : "GET";
string IRestRequest.Endpoint => $"auth/login";
@@ -16,7 +16,7 @@ namespace Discord.API.Client.Rest
public string Password { get; set; }
}
- public sealed class LoginResponse
+ public class LoginResponse
{
[JsonProperty("token")]
public string Token { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/Logout.cs b/src/Discord.Net/API/Client/Rest/Logout.cs
index 5df18dbe1..78f8059e5 100644
--- a/src/Discord.Net/API/Client/Rest/Logout.cs
+++ b/src/Discord.Net/API/Client/Rest/Logout.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class LogoutRequest : IRestRequest
+ public class LogoutRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"auth/logout";
diff --git a/src/Discord.Net/API/Client/Rest/PruneMembers.cs b/src/Discord.Net/API/Client/Rest/PruneMembers.cs
index ea0b86e41..41771f7d6 100644
--- a/src/Discord.Net/API/Client/Rest/PruneMembers.cs
+++ b/src/Discord.Net/API/Client/Rest/PruneMembers.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class PruneMembersRequest : IRestRequest
+ public class PruneMembersRequest : IRestRequest
{
string IRestRequest.Method => IsSimulation ? "GET" : "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/prune?days={Days}";
@@ -21,7 +21,7 @@ namespace Discord.API.Client.Rest
}
}
- public sealed class PruneMembersResponse
+ public class PruneMembersResponse
{
[JsonProperty("pruned")]
public int Pruned { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs
index 3e6e06e90..c704eadbc 100644
--- a/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs
+++ b/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class RemoveChannelPermissionsRequest : IRestRequest
+ public class RemoveChannelPermissionsRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}";
diff --git a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs
index e126f6bc2..c6d48c944 100644
--- a/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs
+++ b/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class RemoveGuildBanRequest : IRestRequest
+ public class RemoveGuildBanRequest : IRestRequest
{
string IRestRequest.Method => "DELETE";
string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}";
diff --git a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs
index 3f768cf07..c481eda43 100644
--- a/src/Discord.Net/API/Client/Rest/ReorderChannels.cs
+++ b/src/Discord.Net/API/Client/Rest/ReorderChannels.cs
@@ -5,7 +5,7 @@ using System.Linq;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class ReorderChannelsRequest : IRestRequest
+ public class ReorderChannelsRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/channels";
@@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest
}
bool IRestRequest.IsPrivate => false;
- public sealed class Channel
+ public class Channel
{
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs
index 5eb9a9d11..23d73541f 100644
--- a/src/Discord.Net/API/Client/Rest/ReorderRoles.cs
+++ b/src/Discord.Net/API/Client/Rest/ReorderRoles.cs
@@ -5,7 +5,7 @@ using System.Linq;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class ReorderRolesRequest : IRestRequest
+ public class ReorderRolesRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles";
@@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest
}
bool IRestRequest.IsPrivate => false;
- public sealed class Role
+ public class Role
{
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; }
diff --git a/src/Discord.Net/API/Client/Rest/SendFile.cs b/src/Discord.Net/API/Client/Rest/SendFile.cs
index 7b1a6b084..8d072d0e3 100644
--- a/src/Discord.Net/API/Client/Rest/SendFile.cs
+++ b/src/Discord.Net/API/Client/Rest/SendFile.cs
@@ -4,7 +4,7 @@ using System.IO;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class SendFileRequest : IRestFileRequest
+ public class SendFileRequest : IRestFileRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages";
diff --git a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs
index abaceb96c..aab017c67 100644
--- a/src/Discord.Net/API/Client/Rest/SendIsTyping.cs
+++ b/src/Discord.Net/API/Client/Rest/SendIsTyping.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class SendIsTypingRequest : IRestRequest
+ public class SendIsTypingRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/typing";
diff --git a/src/Discord.Net/API/Client/Rest/SendMessage.cs b/src/Discord.Net/API/Client/Rest/SendMessage.cs
index c58d00d9e..6c6d1ae10 100644
--- a/src/Discord.Net/API/Client/Rest/SendMessage.cs
+++ b/src/Discord.Net/API/Client/Rest/SendMessage.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class SendMessageRequest : IRestRequest
+ public class SendMessageRequest : IRestRequest
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs
index f09c8ba87..cccd4b096 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateChannel.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateChannel.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateChannelRequest : IRestRequest
+ public class UpdateChannelRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"channels/{ChannelId}";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs
index 163dc437e..4ff530554 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateGuild.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateGuild.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateGuildRequest : IRestRequest
+ public class UpdateGuildRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateMember.cs b/src/Discord.Net/API/Client/Rest/UpdateMember.cs
index 1c90560ac..0bc5274d0 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateMember.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateMember.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateMemberRequest : IRestRequest
+ public class UpdateMemberRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs
index ede0a0797..5b4480a4b 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateMessage.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateMessage.cs
@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateMessageRequest : IRestRequest
+ public class UpdateMessageRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs
index d89e60983..08f28d868 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateProfile.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateProfile.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateProfileRequest : IRestRequest
+ public class UpdateProfileRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"users/@me";
diff --git a/src/Discord.Net/API/Client/Rest/UpdateRole.cs b/src/Discord.Net/API/Client/Rest/UpdateRole.cs
index 9ebc1e76f..7aac774b7 100644
--- a/src/Discord.Net/API/Client/Rest/UpdateRole.cs
+++ b/src/Discord.Net/API/Client/Rest/UpdateRole.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class UpdateRoleRequest : IRestRequest
+ public class UpdateRoleRequest : IRestRequest
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}";
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs
index a9727dd91..349a8a28b 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs
@@ -1,6 +1,6 @@
namespace Discord.API.Client.VoiceSocket
{
- public sealed class HeartbeatCommand : IWebSocketMessage
+ public class HeartbeatCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat;
object IWebSocketMessage.Payload => EpochTime.GetMilliseconds();
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs
index 1836b234c..fbb38b9d0 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.VoiceSocket
{
- public sealed class IdentifyCommand : IWebSocketMessage
+ public class IdentifyCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Identify;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs
index aa8e8127b..d860efe45 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs
@@ -2,13 +2,13 @@
namespace Discord.API.Client.VoiceSocket
{
- public sealed class SelectProtocolCommand : IWebSocketMessage
+ public class SelectProtocolCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.SelectProtocol;
object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false;
- public sealed class Data
+ public class Data
{
[JsonProperty("address")]
public string Address { get; set; }
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs b/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs
index 13ab00524..6022c4d58 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs
@@ -2,7 +2,7 @@
namespace Discord.API.Client.VoiceSocket
{
- public sealed class SetSpeakingCommand : IWebSocketMessage
+ public class SetSpeakingCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.Speaking;
object IWebSocketMessage.Payload => this;
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs
index b0fa34c1d..6fdced897 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs
@@ -2,7 +2,7 @@
namespace Discord.API.Client.VoiceSocket
{
- public sealed class ReadyEvent
+ public class ReadyEvent
{
[JsonProperty("ssrc")]
public uint SSRC { get; set; }
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs
index 13f190cfc..042c5278d 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs
@@ -2,7 +2,7 @@
namespace Discord.API.Client.VoiceSocket
{
- public sealed class SessionDescriptionEvent
+ public class SessionDescriptionEvent
{
[JsonProperty("secret_key")]
public byte[] SecretKey { get; set; }
diff --git a/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs b/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs
index b3de0f800..59268c4e6 100644
--- a/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs
+++ b/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.VoiceSocket
{
- public sealed class SpeakingEvent
+ public class SpeakingEvent
{
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
public ulong UserId { get; set; }
diff --git a/src/Discord.Net/API/Converters.cs b/src/Discord.Net/API/Converters.cs
index 1142e5755..5d80ca99f 100644
--- a/src/Discord.Net/API/Converters.cs
+++ b/src/Discord.Net/API/Converters.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Discord.API.Converters
{
- public sealed class LongStringConverter : JsonConverter
+ public class LongStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(ulong);
@@ -14,7 +14,7 @@ namespace Discord.API.Converters
=> writer.WriteValue(((ulong)value).ToIdString());
}
- public sealed class NullableLongStringConverter : JsonConverter
+ public class NullableLongStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(ulong?);
@@ -24,7 +24,7 @@ namespace Discord.API.Converters
=> writer.WriteValue(((ulong?)value).ToIdString());
}
- /*public sealed class LongStringEnumerableConverter : JsonConverter
+ /*public class LongStringEnumerableConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
@@ -55,7 +55,7 @@ namespace Discord.API.Converters
}
}*/
- internal sealed class LongStringArrayConverter : JsonConverter
+ internal class LongStringArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
diff --git a/src/Discord.Net/API/Status/Common/StatusResult.cs b/src/Discord.Net/API/Status/Common/StatusResult.cs
index 314a180c7..74728c578 100644
--- a/src/Discord.Net/API/Status/Common/StatusResult.cs
+++ b/src/Discord.Net/API/Status/Common/StatusResult.cs
@@ -5,7 +5,7 @@ namespace Discord.API.Status
{
public class StatusResult
{
- public sealed class PageData
+ public class PageData
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -17,7 +17,7 @@ namespace Discord.API.Status
public DateTime? UpdatedAt { get; set; }
}
- public sealed class IncidentData
+ public class IncidentData
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -50,7 +50,7 @@ namespace Discord.API.Status
public IncidentUpdateData[] Updates { get; set; }
}
- public sealed class IncidentUpdateData
+ public class IncidentUpdateData
{
[JsonProperty("id")]
public string Id { get; set; }
diff --git a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs
index 5a8412f96..638c176a5 100644
--- a/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs
+++ b/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Status.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetActiveMaintenancesRequest : IRestRequest
+ public class GetActiveMaintenancesRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"scheduled-maintenances/active.json";
diff --git a/src/Discord.Net/API/Status/Rest/AllIncidents.cs b/src/Discord.Net/API/Status/Rest/AllIncidents.cs
index 13f43e022..81a82ce51 100644
--- a/src/Discord.Net/API/Status/Rest/AllIncidents.cs
+++ b/src/Discord.Net/API/Status/Rest/AllIncidents.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Status.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetAllIncidentsRequest : IRestRequest
+ public class GetAllIncidentsRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"incidents.json";
diff --git a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs
index f07de061c..1665dde75 100644
--- a/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs
+++ b/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Status.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetUnresolvedIncidentsRequest : IRestRequest
+ public class GetUnresolvedIncidentsRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"incidents/unresolved.json";
diff --git a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs
index 769602381..afc812cc9 100644
--- a/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs
+++ b/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs
@@ -3,7 +3,7 @@
namespace Discord.API.Status.Rest
{
[JsonObject(MemberSerialization.OptIn)]
- public sealed class GetUpcomingMaintenancesRequest : IRestRequest
+ public class GetUpcomingMaintenancesRequest : IRestRequest
{
string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"scheduled-maintenances/upcoming.json";
diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs
index 5b620ef38..e34c4efef 100644
--- a/src/Discord.Net/DiscordClient.Events.cs
+++ b/src/Discord.Net/DiscordClient.Events.cs
@@ -9,29 +9,27 @@ namespace Discord
public event EventHandler Disconnected = delegate { };
public event EventHandler ChannelCreated = delegate { };
public event EventHandler ChannelDestroyed = delegate { };
- public event EventHandler ChannelUpdated = delegate { };
+ public event EventHandler ChannelUpdated = delegate { };
public event EventHandler MessageAcknowledged = delegate { };
public event EventHandler MessageDeleted = delegate { };
public event EventHandler MessageReceived = delegate { };
public event EventHandler MessageSent = delegate { };
- public event EventHandler MessageUpdated = delegate { };
- public event EventHandler ProfileUpdated = delegate { };
+ public event EventHandler MessageUpdated = delegate { };
+ public event EventHandler ProfileUpdated = delegate { };
public event EventHandler RoleCreated = delegate { };
- public event EventHandler RoleUpdated = delegate { };
+ public event EventHandler RoleUpdated = delegate { };
public event EventHandler RoleDeleted = delegate { };
public event EventHandler JoinedServer = delegate { };
public event EventHandler LeftServer = delegate { };
public event EventHandler ServerAvailable = delegate { };
- public event EventHandler ServerUpdated = delegate { };
+ public event EventHandler ServerUpdated = delegate { };
public event EventHandler ServerUnavailable = delegate { };
public event EventHandler UserBanned = delegate { };
- public event EventHandler UserIsTypingUpdated = delegate { };
+ public event EventHandler UserIsTyping = delegate { };
public event EventHandler UserJoined = delegate { };
public event EventHandler UserLeft = delegate { };
- public event EventHandler UserPresenceUpdated = delegate { };
- public event EventHandler UserUpdated = delegate { };
+ public event EventHandler UserUpdated = delegate { };
public event EventHandler UserUnbanned = delegate { };
- public event EventHandler UserVoiceStateUpdated = delegate { };
private void OnConnected()
=> OnEvent(Connected);
@@ -42,8 +40,8 @@ namespace Discord
=> OnEvent(ChannelCreated, new ChannelEventArgs(channel));
private void OnChannelDestroyed(Channel channel)
=> OnEvent(ChannelDestroyed, new ChannelEventArgs(channel));
- private void OnChannelUpdated(Channel channel)
- => OnEvent(ChannelUpdated, new ChannelEventArgs(channel));
+ private void OnChannelUpdated(Channel before, Channel after)
+ => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after));
private void OnMessageAcknowledged(Message msg)
=> OnEvent(MessageAcknowledged, new MessageEventArgs(msg));
@@ -53,18 +51,18 @@ namespace Discord
=> OnEvent(MessageReceived, new MessageEventArgs(msg));
internal void OnMessageSent(Message msg)
=> OnEvent(MessageSent, new MessageEventArgs(msg));
- private void OnMessageUpdated(Message msg)
- => OnEvent(MessageUpdated, new MessageEventArgs(msg));
+ private void OnMessageUpdated(Message before, Message after)
+ => OnEvent(MessageUpdated, new MessageUpdatedEventArgs(before, after));
- private void OnProfileUpdated(Profile profile)
- => OnEvent(ProfileUpdated, new ProfileEventArgs(profile));
+ private void OnProfileUpdated(Profile before, Profile after)
+ => OnEvent(ProfileUpdated, new ProfileUpdatedEventArgs(before, after));
private void OnRoleCreated(Role role)
=> OnEvent(RoleCreated, new RoleEventArgs(role));
private void OnRoleDeleted(Role role)
=> OnEvent(RoleDeleted, new RoleEventArgs(role));
- private void OnRoleUpdated(Role role)
- => OnEvent(RoleUpdated, new RoleEventArgs(role));
+ private void OnRoleUpdated(Role before, Role after)
+ => OnEvent(RoleUpdated, new RoleUpdatedEventArgs(before, after));
private void OnJoinedServer(Server server)
=> OnEvent(JoinedServer, new ServerEventArgs(server));
@@ -72,27 +70,23 @@ namespace Discord
=> OnEvent(LeftServer, new ServerEventArgs(server));
private void OnServerAvailable(Server server)
=> OnEvent(ServerAvailable, new ServerEventArgs(server));
- private void OnServerUpdated(Server server)
- => OnEvent(ServerUpdated, new ServerEventArgs(server));
+ private void OnServerUpdated(Server before, Server after)
+ => OnEvent(ServerUpdated, new ServerUpdatedEventArgs(before, after));
private void OnServerUnavailable(Server server)
=> OnEvent(ServerUnavailable, new ServerEventArgs(server));
private void OnUserBanned(User user)
=> OnEvent(UserBanned, new UserEventArgs(user));
private void OnUserIsTypingUpdated(Channel channel, User user)
- => OnEvent(UserIsTypingUpdated, new ChannelUserEventArgs(channel, user));
+ => OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user));
private void OnUserJoined(User user)
=> OnEvent(UserJoined, new UserEventArgs(user));
private void OnUserLeft(User user)
=> OnEvent(UserLeft, new UserEventArgs(user));
- private void OnUserPresenceUpdated(User user)
- => OnEvent(UserPresenceUpdated, new UserEventArgs(user));
private void OnUserUnbanned(User user)
=> OnEvent(UserUnbanned, new UserEventArgs(user));
- private void OnUserUpdated(User user)
- => OnEvent(UserUpdated, new UserEventArgs(user));
- private void OnUserVoiceStateUpdated(User user)
- => OnEvent(UserVoiceStateUpdated, new UserEventArgs(user));
+ private void OnUserUpdated(User before, User after)
+ => OnEvent(UserUpdated, new UserUpdatedEventArgs(before, after));
private void OnEvent(EventHandler handler, T eventArgs, [CallerMemberName] string callerName = null)
{
diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index 282606031..97c36c173 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -31,7 +31,6 @@ namespace Discord
private readonly ConcurrentDictionary _channels;
private readonly ConcurrentDictionary _privateChannels; //Key = RecipientId
private Dictionary _regions;
- private CancellationTokenSource _cancelTokenSource;
internal Logger Logger { get; }
@@ -138,7 +137,7 @@ namespace Discord
//Networking
ClientAPI = new RestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI"));
StatusAPI = new RestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI"));
- GatewaySocket = new GatewaySocket(this, Log.CreateLogger("Gateway"));
+ GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway"));
GatewaySocket.Connected += (s, e) =>
{
if (State == ConnectionState.Connecting)
@@ -148,7 +147,7 @@ namespace Discord
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e);
if (Config.UseMessageQueue)
- MessageQueue = new MessageQueue(this, Log.CreateLogger("MessageQueue"));
+ MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue"));
//Extensibility
Services = new ServiceManager(this);
@@ -182,9 +181,7 @@ namespace Discord
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
- if (State != ConnectionState.Disconnected)
- await Disconnect().ConfigureAwait(false);
- await _taskManager.Stop().ConfigureAwait(false);
+ await Disconnect().ConfigureAwait(false);
_taskManager.ClearException();
Stopwatch stopwatch = null;
@@ -193,19 +190,20 @@ namespace Discord
State = ConnectionState.Connecting;
_disconnectedEvent.Reset();
- _cancelTokenSource = new CancellationTokenSource();
- CancelToken = _cancelTokenSource.Token;
- GatewaySocket.ParentCancelToken = CancelToken;
+ var cancelSource = new CancellationTokenSource();
+ CancelToken = cancelSource.Token;
+ ClientAPI.CancelToken = CancelToken;
+ StatusAPI.CancelToken = CancelToken;
await Login(email, password, token).ConfigureAwait(false);
- await GatewaySocket.Connect().ConfigureAwait(false);
+ await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false);
List tasks = new List();
tasks.Add(CancelToken.Wait());
if (Config.UseMessageQueue)
tasks.Add(MessageQueue.Run(CancelToken, Config.MessageQueueInterval));
- await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false);
+ await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false);
GatewaySocket.WaitForConnection(CancelToken);
if (Config.LogLevel >= LogSeverity.Verbose)
@@ -228,30 +226,28 @@ namespace Discord
byte[] cacheKey = null;
//Get Token
- if (token == null && Config.CacheToken)
+ if (email != null && Config.CacheToken)
{
- Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password,
- new byte[] { 0x5A, 0x2A, 0xF8, 0xCF, 0x78, 0xD3, 0x7D, 0x0D });
- cacheKey = deriveBytes.GetBytes(16);
-
tokenPath = GetTokenCachePath(email);
- oldToken = LoadToken(tokenPath, cacheKey);
- ClientAPI.Token = oldToken;
+ if (token == null && password != null)
+ {
+ Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password,
+ new byte[] { 0x5A, 0x2A, 0xF8, 0xCF, 0x78, 0xD3, 0x7D, 0x0D });
+ cacheKey = deriveBytes.GetBytes(16);
+
+ oldToken = LoadToken(tokenPath, cacheKey);
+ token = oldToken;
+ }
}
- else
- ClientAPI.Token = token;
-
+
+ ClientAPI.Token = token;
var request = new LoginRequest() { Email = email, Password = password };
var response = await ClientAPI.Send(request).ConfigureAwait(false);
token = response.Token;
- if (Config.CacheToken && token != oldToken)
+ if (Config.CacheToken && token != oldToken && tokenPath != null)
SaveToken(tokenPath, cacheKey, token);
-
ClientAPI.Token = token;
- GatewaySocket.Token = token;
- GatewaySocket.SessionId = null;
-
//Cache other stuff
var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false));
_regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port, x.Vip))
@@ -262,7 +258,6 @@ namespace Discord
State = ConnectionState.Connected;
_connectedEvent.Set();
- ClientAPI.CancelToken = CancelToken;
SendStatus();
OnConnected();
}
@@ -275,7 +270,10 @@ namespace Discord
State = ConnectionState.Disconnecting;
if (oldState == ConnectionState.Connected)
- await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false);
+ {
+ try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); }
+ catch (OperationCanceledException) { }
+ }
if (Config.UseMessageQueue)
MessageQueue.Clear();
@@ -283,8 +281,6 @@ namespace Discord
await GatewaySocket.Disconnect().ConfigureAwait(false);
ClientAPI.Token = null;
- GatewaySocket.Token = null;
- GatewaySocket.SessionId = null;
_servers.Clear();
_channels.Clear();
@@ -482,8 +478,6 @@ namespace Discord
if (Config.LogLevel >= LogSeverity.Verbose)
stopwatch = Stopwatch.StartNew();
var data = e.Payload.ToObject(Serializer);
- GatewaySocket.StartHeartbeat(data.HeartbeatInterval);
- GatewaySocket.SessionId = data.SessionId;
SessionId = data.SessionId;
PrivateUser = new User(this, data.User.Id, null);
PrivateUser.Update(data.User);
@@ -510,12 +504,6 @@ namespace Discord
}
}
break;
- case "RESUMED":
- {
- var data = e.Payload.ToObject(Serializer);
- GatewaySocket.StartHeartbeat(data.HeartbeatInterval);
- }
- break;
//Servers
case "GUILD_CREATE":
@@ -546,10 +534,11 @@ namespace Discord
var server = GetServer(data.Id);
if (server != null)
{
+ var before = Config.EnablePreUpdateEvents ? server.Clone() : null;
server.Update(data);
if (Config.LogEvents)
Logger.Info($"Server Updated: {server.Name}");
- OnServerUpdated(server);
+ OnServerUpdated(before, server);
}
else
Logger.Warning("GUILD_UPDATE referenced an unknown guild.");
@@ -609,10 +598,11 @@ namespace Discord
var channel = GetChannel(data.Id);
if (channel != null)
{
+ var before = Config.EnablePreUpdateEvents ? channel.Clone() : null;
channel.Update(data);
if (Config.LogEvents)
Logger.Info($"Channel Updated: {channel.Server?.Name ?? "[Private]"}/{channel.Name}");
- OnChannelUpdated(channel);
+ OnChannelUpdated(before, channel);
}
else
Logger.Warning("CHANNEL_UPDATE referenced an unknown channel.");
@@ -660,10 +650,11 @@ namespace Discord
var user = server.GetUser(data.User.Id);
if (user != null)
{
+ var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
if (Config.LogEvents)
Logger.Info($"User Updated: {server.Name}/{user.Name}");
- OnUserUpdated(user);
+ OnUserUpdated(before, user);
}
else
Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user.");
@@ -721,7 +712,7 @@ namespace Discord
role.Update(data.Data);
if (Config.LogEvents)
Logger.Info($"Role Created: {server.Name}/{role.Name}");
- OnRoleUpdated(role);
+ OnRoleCreated(role);
}
else
Logger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");
@@ -736,10 +727,11 @@ namespace Discord
var role = server.GetRole(data.Data.Id);
if (role != null)
{
+ var before = Config.EnablePreUpdateEvents ? role.Clone() : null;
role.Update(data.Data);
if (Config.LogEvents)
Logger.Info($"Role Updated: {server.Name}/{role.Name}");
- OnRoleUpdated(role);
+ OnRoleUpdated(before, role);
}
else
Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown role.");
@@ -860,10 +852,11 @@ namespace Discord
if (channel != null)
{
var msg = channel.GetMessage(data.Id, data.Author?.Id);
+ var before = Config.EnablePreUpdateEvents ? msg.Clone() : null;
msg.Update(data);
if (Config.LogEvents)
Logger.Verbose($"Message Update: {channel.Server?.Name ?? "[Private]"}/{channel.Name}");
- OnMessageUpdated(msg);
+ OnMessageUpdated(before, msg);
}
else
Logger.Warning("MESSAGE_UPDATE referenced an unknown channel.");
@@ -936,9 +929,10 @@ namespace Discord
if (user != null)
{
+ var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
//Logger.Verbose($"Presence Updated: {server.Name}/{user.Name}");
- OnUserPresenceUpdated(user);
+ OnUserUpdated(before, user);
}
/*else //Occurs when a user leaves a server
Logger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/
@@ -982,9 +976,10 @@ namespace Discord
var user = server.GetUser(data.UserId);
if (user != null)
{
+ var before = Config.EnablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
//Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}");
- OnUserVoiceStateUpdated(user);
+ OnUserUpdated(before, user);
}
/*else //Occurs when a user leaves a server
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");*/
@@ -1000,17 +995,22 @@ namespace Discord
var data = e.Payload.ToObject(Serializer);
if (data.Id == CurrentUser.Id)
{
+ var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null;
CurrentUser.Update(data);
PrivateUser.Update(data);
foreach (var server in _servers)
server.Value.CurrentUser.Update(data);
if (Config.LogEvents)
Logger.Info("Profile Updated");
- OnProfileUpdated(CurrentUser);
+ OnProfileUpdated(before, CurrentUser);
}
}
break;
+ //Handled in GatewaySocket
+ case "RESUMED":
+ break;
+
//Ignored
case "USER_SETTINGS_UPDATE":
case "GUILD_INTEGRATIONS_UPDATE":
@@ -1032,17 +1032,13 @@ namespace Discord
#endregion
#region Async Wrapper
- /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
- public void Run(Func asyncAction)
+ /// Blocking call that will execute the provided async method and wait until the client has been manually stopped. This is mainly intended for use in console applications.
+ public void ExecuteAndWait(Func asyncAction)
{
- try
- {
- AsyncContext.Run(asyncAction);
- }
- catch (TaskCanceledException) { }
+ asyncAction().GetAwaiter().GetResult();
_disconnectedEvent.WaitOne();
}
- /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
+ /// Blocking call and wait until the client has been manually stopped. This is mainly intended for use in console applications.
public void Wait()
{
_disconnectedEvent.WaitOne();
diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs
index f3ff9567f..89f746fcd 100644
--- a/src/Discord.Net/DiscordConfig.cs
+++ b/src/Discord.Net/DiscordConfig.cs
@@ -57,7 +57,6 @@ namespace Discord
private DiscordMode _mode = DiscordMode.Bot;
/// User Agent string to use when connecting to Discord.
- [JsonIgnore]
public string UserAgent { get; private set; }
//Rest
@@ -103,6 +102,9 @@ namespace Discord
/// Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members
public bool UsePermissionsCache { get { return _usePermissionsCache; } set { SetValue(ref _usePermissionsCache, value); } }
private bool _usePermissionsCache = true;
+ /// Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed.
+ public bool EnablePreUpdateEvents { get { return _enablePreUpdateEvents; } set { SetValue(ref _enablePreUpdateEvents, value); } }
+ private bool _enablePreUpdateEvents = true;
public DiscordConfig()
{
diff --git a/src/Discord.Net/DynamicIL.cs b/src/Discord.Net/DynamicIL.cs
new file mode 100644
index 000000000..bce89424d
--- /dev/null
+++ b/src/Discord.Net/DynamicIL.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Discord
+{
+ internal static class DynamicIL
+ {
+ public static Action CreateCopyMethod()
+ {
+ var method = new DynamicMethod("CopyTo", null, new[] { typeof(T), typeof(T) }, typeof(T), true);
+ var generator = method.GetILGenerator();
+ var typeInfo = typeof(T).GetTypeInfo();
+
+ typeInfo.ForEachField(f =>
+ {
+ generator.Emit(OpCodes.Ldarg_1); //Stack: TargetRef
+ generator.Emit(OpCodes.Ldarg_0); //Stack: TargetRef, SourceRef
+ generator.Emit(OpCodes.Ldfld, f); //Stack: TargetRef, Value
+ generator.Emit(OpCodes.Stfld, f); //Stack:
+ });
+
+ generator.Emit(OpCodes.Ret);
+
+ return method.CreateDelegate(typeof(Action)) as Action;
+ }
+
+ public static void ForEachField(this TypeInfo typeInfo, Action func)
+ {
+ var baseType = typeInfo.BaseType;
+ if (baseType != null)
+ baseType.GetTypeInfo().ForEachField(func);
+
+ foreach (var field in typeInfo.DeclaredFields.Where(x => !x.IsStatic))
+ func(field);
+ }
+ public static void ForEachProperty(this TypeInfo typeInfo, Action func)
+ {
+ var baseType = typeInfo.BaseType;
+ if (baseType != null)
+ baseType.GetTypeInfo().ForEachProperty(func);
+
+ foreach (var prop in typeInfo.DeclaredProperties.Where(x =>
+ (!x.CanRead || !x.GetMethod.IsStatic) && (!x.CanWrite || !x.SetMethod.IsStatic)))
+ func(prop);
+ }
+ }
+}
diff --git a/src/Discord.Net/ETF/ETFReader.cs b/src/Discord.Net/ETF/ETFReader.cs
new file mode 100644
index 000000000..d2f33dcbc
--- /dev/null
+++ b/src/Discord.Net/ETF/ETFReader.cs
@@ -0,0 +1,9 @@
+using System;
+using System.IO;
+
+namespace Discord.ETF
+{
+ public class ETFReader
+ {
+ }
+}
diff --git a/src/Discord.Net/ETF/ETFType.cs b/src/Discord.Net/ETF/ETFType.cs
new file mode 100644
index 000000000..53499d5fa
--- /dev/null
+++ b/src/Discord.Net/ETF/ETFType.cs
@@ -0,0 +1,32 @@
+namespace Discord.ETF
+{
+ public enum ETFType : byte
+ {
+ NEW_FLOAT_EXT = 70,
+ BIT_BINARY_EXT = 77,
+ ATOM_CACHE_REF = 82,
+ SMALL_INTEGER_EXT = 97,
+ INTEGER_EXT = 98,
+ FLOAT_EXT = 99,
+ ATOM_EXT = 100,
+ REFERENCE_EXT = 101,
+ PORT_EXT = 102,
+ PID_EXT = 103,
+ SMALL_TUPLE_EXT = 104,
+ LARGE_TUPLE_EXT = 105,
+ NIL_EXT = 106,
+ STRING_EXT = 107,
+ LIST_EXT = 108,
+ BINARY_EXT = 109,
+ SMALL_BIG_EXT = 110,
+ LARGE_BIG_EXT = 111,
+ NEW_FUN_EXT = 112,
+ EXPORT_EXT = 113,
+ NEW_REFERENCE_EXT = 114,
+ SMALL_ATOM_EXT = 115,
+ MAP_EXT = 116,
+ FUN_EXT = 117,
+ ATOM_UTF8_EXT = 118,
+ SMALL_ATOM_UTF8_EXT = 119
+ }
+}
diff --git a/src/Discord.Net/ETF/ETFWriter.cs b/src/Discord.Net/ETF/ETFWriter.cs
new file mode 100644
index 000000000..06641e664
--- /dev/null
+++ b/src/Discord.Net/ETF/ETFWriter.cs
@@ -0,0 +1,425 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Text;
+
+namespace Discord.ETF
+{
+ public unsafe class ETFWriter : IDisposable
+ {
+ private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' };
+ private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' };
+ private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' };
+
+ private readonly static MethodInfo _writeTMethod = typeof(ETFWriter).GetTypeInfo()
+ .GetDeclaredMethods(nameof(Write))
+ .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0])
+ .Single();
+ private readonly static MethodInfo _writeNullableTMethod = typeof(ETFWriter).GetTypeInfo()
+ .GetDeclaredMethods(nameof(Write))
+ .Where(x =>
+ {
+ if (!x.IsGenericMethodDefinition) return false;
+ var p = x.GetParameters()[0].ParameterType.GetTypeInfo();
+ return p.IsGenericType && p.GetGenericTypeDefinition() == typeof(Nullable<>);
+ })
+ .Single();
+ private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private readonly Stream _stream;
+ private readonly byte[] _buffer;
+ private readonly bool _leaveOpen;
+ private readonly Encoding _encoding;
+ private readonly ConcurrentDictionary _serializers, _indirectSerializers;
+
+ public virtual Stream BaseStream
+ {
+ get
+ {
+ Flush();
+ return _stream;
+ }
+ }
+
+ public ETFWriter(Stream stream, bool leaveOpen = false)
+ {
+ if (stream == null) throw new ArgumentNullException(nameof(stream));
+
+ _stream = stream;
+ _leaveOpen = leaveOpen;
+ _buffer = new byte[11];
+ _encoding = Encoding.UTF8;
+ _serializers = new ConcurrentDictionary();
+ _indirectSerializers = new ConcurrentDictionary();
+ }
+
+ enum EnumTest1 { A, B, C }
+ public static byte[] Test()
+ {
+ using (var stream = new MemoryStream())
+ {
+ using (var writer = new ETFWriter(stream))
+ {
+ var request = new API.Client.Rest.SendMessageRequest(109384029348)
+ {
+ Content = "TestMsg",
+ Nonce = null,
+ IsTTS = false
+ };
+ writer.Write(request);
+ /*writer.Write((EnumTest1?)EnumTest1.C);
+ writer.Write((object)(EnumTest1?)EnumTest1.C);
+ writer.Write((EnumTest1?)null);
+ writer.Write((object)(EnumTest1?)null);*/
+ }
+ return stream.ToArray();
+ }
+ }
+
+ public void Write(bool value)
+ {
+ if (value)
+ _stream.Write(_trueBytes, 0, _trueBytes.Length);
+ else
+ _stream.Write(_falseBytes, 0, _falseBytes.Length);
+ }
+
+ public void Write(sbyte value) => Write((long)value);
+ public void Write(byte value) => Write((ulong)value);
+ public void Write(short value) => Write((long)value);
+ public void Write(ushort value) => Write((ulong)value);
+ public void Write(int value) => Write((long)value);
+ public void Write(uint value) => Write((ulong)value);
+ public void Write(long value)
+ {
+ if (value >= byte.MinValue && value <= byte.MaxValue)
+ {
+ _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT;
+ _buffer[1] = (byte)value;
+ _stream.Write(_buffer, 0, 2);
+ }
+ else if (value >= int.MinValue && value <= int.MaxValue)
+ {
+ _buffer[0] = (byte)ETFType.INTEGER_EXT;
+ _buffer[1] = (byte)((value >> 24) & 0xFF);
+ _buffer[2] = (byte)((value >> 16) & 0xFF);
+ _buffer[3] = (byte)((value >> 8) & 0xFF);
+ _buffer[4] = (byte)(value & 0xFF);
+ _stream.Write(_buffer, 0, 5);
+ }
+ else
+ {
+ _buffer[0] = (byte)ETFType.SMALL_BIG_EXT;
+ if (value < 0)
+ {
+ _buffer[2] = 1; //Is negative
+ value = -value;
+ }
+
+ byte bytes = 0;
+ while (value > 0)
+ _buffer[3 + bytes++] = (byte)((value >>= 8) & 0xFF);
+ _buffer[1] = bytes; //Encoded bytes
+
+ _stream.Write(_buffer, 0, 3 + bytes);
+ }
+ }
+ public void Write(ulong value)
+ {
+ if (value <= byte.MaxValue)
+ {
+ _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT;
+ _buffer[1] = (byte)value;
+ _stream.Write(_buffer, 0, 2);
+ }
+ else if (value <= int.MaxValue)
+ {
+ _buffer[0] = (byte)ETFType.INTEGER_EXT;
+ _buffer[1] = (byte)((value >> 24) & 0xFF);
+ _buffer[2] = (byte)((value >> 16) & 0xFF);
+ _buffer[3] = (byte)((value >> 8) & 0xFF);
+ _buffer[4] = (byte)(value & 0xFF);
+ _stream.Write(_buffer, 0, 5);
+ }
+ else
+ {
+ _buffer[0] = (byte)ETFType.SMALL_BIG_EXT;
+ _buffer[2] = 0; //Always positive
+
+ byte bytes = 0;
+ while (value > 0)
+ _buffer[3 + bytes++] = (byte)((value >>= 8) & 0xFF);
+ _buffer[1] = bytes; //Encoded bytes
+
+ _stream.Write(_buffer, 0, 3 + bytes);
+ }
+ }
+
+ public void Write(float value) => Write((double)value);
+ public void Write(double value)
+ {
+ ulong value2 = *(ulong*)&value;
+ _buffer[0] = (byte)ETFType.NEW_FLOAT_EXT;
+ _buffer[1] = (byte)((value2 >> 56) & 0xFF);
+ _buffer[2] = (byte)((value2 >> 48) & 0xFF);
+ _buffer[3] = (byte)((value2 >> 40) & 0xFF);
+ _buffer[4] = (byte)((value2 >> 32) & 0xFF);
+ _buffer[5] = (byte)((value2 >> 24) & 0xFF);
+ _buffer[6] = (byte)((value2 >> 16) & 0xFF);
+ _buffer[7] = (byte)((value2 >> 8) & 0xFF);
+ _buffer[8] = (byte)(value2 & 0xFF);
+ _stream.Write(_buffer, 0, 9);
+ }
+
+ public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerMillisecond));
+
+ public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
+ public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); }
+ public void Write(ushort? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
+ public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); }
+ public void Write(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); }
+ public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
+ public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
+ public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
+ public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); }
+ public void Write(double? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
+ public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); }
+
+ public void Write(byte[] value)
+ {
+ if (value != null)
+ {
+ int count = value.Length;
+ _buffer[0] = (byte)ETFType.BINARY_EXT;
+ _buffer[1] = (byte)((count >> 24) & 0xFF);
+ _buffer[2] = (byte)((count >> 16) & 0xFF);
+ _buffer[3] = (byte)((count >> 8) & 0xFF);
+ _buffer[4] = (byte)(count & 0xFF);
+ _stream.Write(_buffer, 0, 5);
+ _stream.Write(value, 0, value.Length);
+ }
+ else
+ WriteNil();
+ }
+ public void Write(string value)
+ {
+ if (value != null)
+ {
+ var bytes = _encoding.GetBytes(value);
+ int count = bytes.Length;
+ _buffer[0] = (byte)ETFType.BINARY_EXT;
+ _buffer[1] = (byte)((count >> 24) & 0xFF);
+ _buffer[2] = (byte)((count >> 16) & 0xFF);
+ _buffer[3] = (byte)((count >> 8) & 0xFF);
+ _buffer[4] = (byte)(count & 0xFF);
+ _stream.Write(_buffer, 0, 5);
+ _stream.Write(bytes, 0, bytes.Length);
+ }
+ else
+ WriteNil();
+ }
+
+ public void Write(T obj)
+ {
+ var type = typeof(T);
+ var typeInfo = type.GetTypeInfo();
+ var action = _serializers.GetOrAdd(type, _ => CreateSerializer(type, typeInfo, false)) as Action;
+ action(this, obj);
+ }
+ public void Write(T? obj)
+ where T : struct
+ {
+ if (obj != null)
+ Write(obj.Value);
+ else
+ WriteNil();
+ }
+ public void Write(object obj)
+ {
+ if (obj != null)
+ {
+ var type = obj.GetType();
+ var typeInfo = type.GetTypeInfo();
+ var action = _indirectSerializers.GetOrAdd(type, _ => CreateSerializer