Browse Source

Rewrote AudioClient, fixed several async issues, removed most sealed keywords.

tags/docs-0.9
RogueException 9 years ago
parent
commit
0db0675cb5
100 changed files with 675 additions and 395 deletions
  1. +5
    -5
      src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj
  2. +167
    -99
      src/Discord.Net.Audio/AudioClient.cs
  3. +242
    -0
      src/Discord.Net.Audio/AudioClient.cs.old
  4. +70
    -69
      src/Discord.Net.Audio/AudioService.cs
  5. +36
    -26
      src/Discord.Net.Audio/Net/VoiceSocket.cs
  6. +0
    -72
      src/Discord.Net.Audio/SimpleAudioClient.cs
  7. +29
    -0
      src/Discord.Net.Audio/VirtualClient.cs
  8. +2
    -1
      src/Discord.Net.Commands/Command.cs
  9. +12
    -11
      src/Discord.Net.Commands/CommandBuilder.cs
  10. +2
    -2
      src/Discord.Net.Commands/CommandParameter.cs
  11. +1
    -1
      src/Discord.Net.Modules/ModuleManager.cs
  12. +1
    -1
      src/Discord.Net/API/Client/Common/Channel.cs
  13. +1
    -1
      src/Discord.Net/API/Client/Common/ExtendedGuild.cs
  14. +1
    -1
      src/Discord.Net/API/Client/Common/Guild.cs
  15. +1
    -1
      src/Discord.Net/API/Client/Common/InviteReference.cs
  16. +1
    -1
      src/Discord.Net/API/Client/Common/MemberPresence.cs
  17. +5
    -5
      src/Discord.Net/API/Client/Common/Message.cs
  18. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs
  19. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs
  20. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs
  21. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs
  22. +2
    -2
      src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs
  23. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
  24. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs
  25. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs
  26. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs
  27. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs
  28. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs
  29. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs
  30. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs
  31. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs
  32. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs
  33. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs
  34. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs
  35. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs
  36. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs
  37. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs
  38. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs
  39. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs
  40. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs
  41. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs
  42. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs
  43. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs
  44. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs
  45. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs
  46. +2
    -2
      src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs
  47. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/Redirect.cs
  48. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs
  49. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs
  50. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs
  51. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs
  52. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs
  53. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs
  54. +1
    -1
      src/Discord.Net/API/Client/IWebSocketMessage.cs
  55. +1
    -1
      src/Discord.Net/API/Client/Rest/AcceptInvite.cs
  56. +1
    -1
      src/Discord.Net/API/Client/Rest/AckMessage.cs
  57. +1
    -1
      src/Discord.Net/API/Client/Rest/AddChannelPermission.cs
  58. +1
    -1
      src/Discord.Net/API/Client/Rest/AddGuildBan.cs
  59. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateChannel.cs
  60. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateGuild.cs
  61. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateInvite.cs
  62. +1
    -1
      src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs
  63. +1
    -1
      src/Discord.Net/API/Client/Rest/CreateRole.cs
  64. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteChannel.cs
  65. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteInvite.cs
  66. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteMessage.cs
  67. +1
    -1
      src/Discord.Net/API/Client/Rest/DeleteRole.cs
  68. +2
    -2
      src/Discord.Net/API/Client/Rest/Gateway.cs
  69. +1
    -1
      src/Discord.Net/API/Client/Rest/GetBans.cs
  70. +1
    -1
      src/Discord.Net/API/Client/Rest/GetInvite.cs
  71. +1
    -1
      src/Discord.Net/API/Client/Rest/GetInvites.cs
  72. +1
    -1
      src/Discord.Net/API/Client/Rest/GetMessages.cs
  73. +2
    -2
      src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs
  74. +5
    -5
      src/Discord.Net/API/Client/Rest/GetWidget.cs
  75. +1
    -1
      src/Discord.Net/API/Client/Rest/KickMember.cs
  76. +1
    -1
      src/Discord.Net/API/Client/Rest/LeaveGuild.cs
  77. +2
    -2
      src/Discord.Net/API/Client/Rest/Login.cs
  78. +1
    -1
      src/Discord.Net/API/Client/Rest/Logout.cs
  79. +2
    -2
      src/Discord.Net/API/Client/Rest/PruneMembers.cs
  80. +1
    -1
      src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs
  81. +1
    -1
      src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs
  82. +2
    -2
      src/Discord.Net/API/Client/Rest/ReorderChannels.cs
  83. +2
    -2
      src/Discord.Net/API/Client/Rest/ReorderRoles.cs
  84. +1
    -1
      src/Discord.Net/API/Client/Rest/SendFile.cs
  85. +1
    -1
      src/Discord.Net/API/Client/Rest/SendIsTyping.cs
  86. +1
    -1
      src/Discord.Net/API/Client/Rest/SendMessage.cs
  87. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateChannel.cs
  88. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateGuild.cs
  89. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateMember.cs
  90. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateMessage.cs
  91. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateProfile.cs
  92. +1
    -1
      src/Discord.Net/API/Client/Rest/UpdateRole.cs
  93. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs
  94. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs
  95. +2
    -2
      src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs
  96. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs
  97. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs
  98. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs
  99. +1
    -1
      src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs
  100. +4
    -4
      src/Discord.Net/API/Converters.cs

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

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


+ 167
- 99
src/Discord.Net.Audio/AudioClient.cs View File

@@ -1,9 +1,12 @@
using Discord.API.Client.GatewaySocket;
using Discord.API.Client.Rest;
using Discord.Logging;
using Discord.Net.Rest;
using Discord.Net.WebSockets;
using Newtonsoft.Json;
using Nito.AsyncEx;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -41,124 +44,190 @@ 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; }

/// <summary> Gets the unique identifier for this client. </summary>
public int Id { get; }
/// <summary> Gets the service managing this client. </summary>
public AudioService Service { get; }
/// <summary> Gets the configuration object used to make this client. </summary>
public AudioServiceConfig Config { get; }
/// <summary> Gets the internal RestClient for the Client API endpoint. </summary>
public RestClient ClientAPI { get; }
/// <summary> Gets the internal WebSocket for the Gateway event stream. </summary>
public GatewaySocket GatewaySocket { get; }
public VoiceWebSocket VoiceSocket { get; }
/// <summary> Gets the internal WebSocket for the Voice control stream. </summary>
public VoiceSocket VoiceSocket { get; }
/// <summary> Gets the JSON serializer used by this client. </summary>
public JsonSerializer Serializer { get; }
/// <summary> </summary>
public Stream OutputStream { get; }

/// <summary> Gets a cancellation token that triggers when the client is manually disconnected. </summary>
public CancellationToken CancelToken { get; private set; }
/// <summary> Gets the session id for the current connection. </summary>
public string SessionId { get; private set; }

/// <summary> Gets the current state of this client. </summary>
public ConnectionState State => VoiceSocket.State;
/// <summary> Gets the server this client is bound to. </summary>
public Server Server => VoiceSocket.Server;
/// <summary> Gets the channel </summary>
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))
/// <summary> Connects to the Discord server with the provided token. </summary>
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)
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
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 +235,11 @@ namespace Discord.Audio
{
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{
if (data.ChannelId == null)
await Disconnect();
await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value);
@@ -179,7 +248,7 @@ namespace Discord.Audio
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect();
await Disconnect().ConfigureAwait(false);
}
}
}
@@ -187,13 +256,16 @@ namespace Discord.Audio
break;
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
var client = Service.Client;
VoiceSocket.Token = data.Token;
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect().ConfigureAwait(false);
var id = client.CurrentUser?.Id;
if (id != null)
{
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false);
}
}
}
break;
@@ -233,15 +305,11 @@ namespace Discord.Audio
VoiceSocket.WaitForQueue();
}

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

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

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

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

private readonly AudioClient _client;

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

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

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

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

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

GatewaySocket.ReceivedDispatch += OnReceivedDispatch;

VoiceSocket = new VoiceSocket(service.Client.Config, service.Config, service.Client.Serializer, logger);
VoiceSocket.Server = server;

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

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

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

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

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

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

await _taskManager.Start(tasks, cancelSource);
}

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

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

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

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

private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
case "VOICE_STATE_UPDATE":
{
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{
if (data.ChannelId == null)
await Disconnect().ConfigureAwait(false);
else
{
var channel = Service.Client.GetChannel(data.ChannelId.Value);
if (channel != null)
VoiceSocket.Channel = channel;
else
{
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect().ConfigureAwait(false);
}
}
}
}
break;
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
if (data.GuildId == VoiceSocket.Server?.Id)
{
var client = Service.Client;
var id = client.CurrentUser?.Id;
if (id != null)
{
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, _cancelToken).ConfigureAwait(false);
}
}
}
break;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
}

/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
/// <param name="count">Number of bytes in this frame. </param>
public void Send(byte[] data, int offset, int count)
{
if (data == null) throw new ArgumentException(nameof(data));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (VoiceSocket.Server == null) return; //Has been closed
if (count == 0) return;

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

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

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

public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{
GatewaySocket.SendUpdateVoice(serverId, channelId,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
}
}
}

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

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

@@ -30,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<ulong, IAudioClient>();
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>();
else
{
var logger = Client.Log.CreateLogger("Voice");
_defaultClient = new SimpleAudioClient(this, 0, logger);
_defaultClient = new AudioClient(Client, null, 0);
}
_talkingUsers = new ConcurrentDictionary<User, bool>();

@@ -75,68 +79,30 @@ namespace Discord.Audio
{
if (server == null) throw new ArgumentNullException(nameof(server));

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

private readonly AsyncLock _connectionLock;

internal VirtualClient CurrentClient { get; private set; }

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

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

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

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

@@ -0,0 +1,29 @@
using System.IO;
using System.Threading.Tasks;

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

public Server Server { get; }

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

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

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

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

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

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

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


+ 12
- 11
src/Discord.Net.Commands/CommandBuilder.cs View File

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

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

public CommandService Service => _service;

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

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

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

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

_allowRequiredParams = true;
@@ -112,7 +116,7 @@ namespace Discord.Commands
return prefix;
}
}
public sealed class CommandGroupBuilder
public class CommandGroupBuilder
{
private readonly CommandService _service;
private readonly string _prefix;
@@ -154,9 +158,6 @@ namespace Discord.Commands
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);
}
}

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

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

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


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

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

namespace Discord.Modules
{
public sealed class ModuleManager
public class ModuleManager
{
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { };
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { };


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

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


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

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


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

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


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

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


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

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


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

@@ -5,7 +5,7 @@ namespace Discord.API.Client
{
public class Message : MessageReference
{
public sealed class Attachment
public class Attachment
{
[JsonProperty("id")]
public string Id { get; set; }
@@ -23,9 +23,9 @@ namespace Discord.API.Client
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,7 +33,7 @@ namespace Discord.API.Client
public string Name { get; set; }
}

public sealed class ThumbnailInfo
public class ThumbnailInfo
{
[JsonProperty("url")]
public string Url { get; set; }
@@ -44,7 +44,7 @@ namespace Discord.API.Client
[JsonProperty("height")]
public int Height { get; set; }
}
public sealed class VideoInfo
public class VideoInfo
{
[JsonProperty("url")]
public string Url { get; set; }


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

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


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

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


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

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


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

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


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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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


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

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

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


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

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

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

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

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

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

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


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

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

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

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


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

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


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

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


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

@@ -4,7 +4,7 @@ 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/{TargetId}";


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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

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


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

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


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

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


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

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class LoginRequest : IRestRequest<LoginResponse>
public class LoginRequest : IRestRequest<LoginResponse>
{
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; }


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

@@ -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";


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

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class PruneMembersRequest : IRestRequest<PruneMembersResponse>
public class PruneMembersRequest : IRestRequest<PruneMembersResponse>
{
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; }


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

@@ -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}";


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

@@ -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}";


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

@@ -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; }


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

@@ -5,7 +5,7 @@ using System.Linq;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class ReorderRolesRequest : IRestRequest<Role[]>
public class ReorderRolesRequest : IRestRequest<Role[]>
{
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; }


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

@@ -4,7 +4,7 @@ using System.IO;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class SendFileRequest : IRestFileRequest<Message>
public class SendFileRequest : IRestFileRequest<Message>
{
string IRestRequest.Method => "POST";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages";


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

@@ -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";


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

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


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

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


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

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


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

@@ -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}";


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

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateMessageRequest : IRestRequest<Message>
public class UpdateMessageRequest : IRestRequest<Message>
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}";


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

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest
{
[JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateProfileRequest : IRestRequest<User>
public class UpdateProfileRequest : IRestRequest<User>
{
string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"users/@me";


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

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


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

@@ -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();


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

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


+ 2
- 2
src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs View File

@@ -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; }


+ 1
- 1
src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs View File

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


+ 1
- 1
src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs View File

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

namespace Discord.API.Client.VoiceSocket
{
public sealed class ReadyEvent
public class ReadyEvent
{
[JsonProperty("ssrc")]
public uint SSRC { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs View File

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

namespace Discord.API.Client.VoiceSocket
{
public sealed class SessionDescriptionEvent
public class SessionDescriptionEvent
{
[JsonProperty("secret_key")]
public byte[] SecretKey { get; set; }


+ 1
- 1
src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs View File

@@ -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; }


+ 4
- 4
src/Discord.Net/API/Converters.cs View File

@@ -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<ulong>);
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<ulong[]>);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)


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

Loading…
Cancel
Save