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

This commit is contained in:
RogueException
2016-01-19 08:31:45 -04:00
parent fab54563e9
commit 0db0675cb5
135 changed files with 856 additions and 566 deletions

View File

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

View File

@@ -1,9 +1,12 @@
using Discord.API.Client.GatewaySocket; using Discord.API.Client.GatewaySocket;
using Discord.API.Client.Rest;
using Discord.Logging; using Discord.Logging;
using Discord.Net.Rest;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using Nito.AsyncEx; using Nito.AsyncEx;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -41,124 +44,190 @@ namespace Discord.Audio
} }
} }
private readonly DiscordConfig _config;
private readonly AsyncLock _connectionLock; private readonly AsyncLock _connectionLock;
private readonly JsonSerializer _serializer; private readonly TaskManager _taskManager;
private CancellationTokenSource _cancelTokenSource; private ConnectionState _gatewayState;
internal AudioService Service { get; }
internal Logger Logger { get; } internal Logger Logger { get; }
/// <summary> Gets the unique identifier for this client. </summary>
public int Id { get; } 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 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; } 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; public ConnectionState State => VoiceSocket.State;
/// <summary> Gets the server this client is bound to. </summary>
public Server Server => VoiceSocket.Server; public Server Server => VoiceSocket.Server;
/// <summary> Gets the channel </summary>
public Channel Channel => VoiceSocket.Channel; public Channel Channel => VoiceSocket.Channel;
public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, Logger logger) public AudioClient(DiscordClient client, Server server, int id)
{ {
Service = service; Id = id;
_serializer = service.Client.Serializer; _config = client.Config;
Id = clientId; Service = client.Audio();
GatewaySocket = gatewaySocket; Config = Service.Config;
Logger = logger; Serializer = client.Serializer;
OutputStream = new OutStream(this); _gatewayState = (int)ConnectionState.Disconnected;
//Logging
Logger = client.Log.CreateLogger($"AudioClient #{id}");
//Async
_taskManager = new TaskManager(Cleanup, false);
_connectionLock = new AsyncLock(); _connectionLock = new AsyncLock();
CancelToken = new CancellationToken(true);
GatewaySocket.ReceivedDispatch += OnReceivedDispatch; //Networking
if (Config.EnableMultiserver)
VoiceSocket = new VoiceWebSocket(service.Client, this, logger); {
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.Server = server;
OutputStream = new OutStream(this);
}
/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); /// <summary> Connects to the Discord server with the provided token. </summary>
_voiceSocket.Disconnected += async (s, e) => public async Task Connect()
{ {
_voiceSocket.CurrentServerId; if (Config.EnableMultiserver)
if (voiceServerId != null) await BeginGatewayConnect().ConfigureAwait(false);
_gatewaySocket.SendLeaveVoice(voiceServerId.Value); else
await _voiceSocket.Disconnect().ConfigureAwait(false); {
RaiseVoiceDisconnected(socket.CurrentServerId.Value, e); var cancelSource = new CancellationTokenSource();
if (e.WasUnexpected) CancelToken = cancelSource.Token;
await socket.Reconnect().ConfigureAwait(false); await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false);
};*/ }
}
private async Task BeginGatewayConnect()
{
try
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
await Disconnect().ConfigureAwait(false);
_taskManager.ClearException();
/*_voiceSocket.IsSpeaking += (s, e) => ClientAPI.Token = Service.Client.ClientAPI.Token;
{
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) => Stopwatch stopwatch = null;
{ if (_config.LogLevel >= LogSeverity.Verbose)
_voiceSocket.ParentCancelToken = _cancelToken; 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;
}
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
if (Config.EnableMultiserver)
ClientAPI.Token = null;
}
private async Task Cleanup()
{
var oldState = _gatewayState;
_gatewayState = ConnectionState.Disconnecting;
if (Config.EnableMultiserver)
{
if (oldState == ConnectionState.Connected)
{
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 Join(Channel channel) public async Task Join(Channel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (channel.Type != ChannelType.Voice) if (channel.Type != ChannelType.Voice)
throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); 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 (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) if (VoiceSocket.Server == null)
throw new InvalidOperationException("This client has been closed."); throw new InvalidOperationException("This client has been closed.");
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
VoiceSocket.Channel = channel;
await Task.Run(() => SendVoiceUpdate(channel.Server.Id, channel.Id);
{
SendVoiceUpdate();
VoiceSocket.WaitForConnection(_cancelTokenSource.Token);
});
}
}
public async Task Connect(bool connectGateway)
{
using (await _connectionLock.LockAsync().ConfigureAwait(false)) using (await _connectionLock.LockAsync().ConfigureAwait(false))
{ await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken));
_cancelTokenSource = new CancellationTokenSource();
var cancelToken = _cancelTokenSource.Token;
VoiceSocket.ParentCancelToken = cancelToken;
if (connectGateway)
{
GatewaySocket.ParentCancelToken = cancelToken;
await GatewaySocket.Connect().ConfigureAwait(false);
GatewaySocket.WaitForConnection(cancelToken);
}
}
} }
public async Task Disconnect() private async void OnReceivedEvent(WebSocketEventEventArgs e)
{
using (await _connectionLock.LockAsync().ConfigureAwait(false))
{
await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false);
VoiceSocket.Channel = null;
SendVoiceUpdate();
await VoiceSocket.Disconnect();
}
}
private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
{ {
try try
{ {
@@ -166,11 +235,11 @@ namespace Discord.Audio
{ {
case "VOICE_STATE_UPDATE": case "VOICE_STATE_UPDATE":
{ {
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer); var data = e.Payload.ToObject<VoiceStateUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id)
{ {
if (data.ChannelId == null) if (data.ChannelId == null)
await Disconnect(); await Disconnect().ConfigureAwait(false);
else else
{ {
var channel = Service.Client.GetChannel(data.ChannelId.Value); var channel = Service.Client.GetChannel(data.ChannelId.Value);
@@ -179,7 +248,7 @@ namespace Discord.Audio
else else
{ {
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting.");
await Disconnect(); await Disconnect().ConfigureAwait(false);
} }
} }
} }
@@ -187,13 +256,16 @@ namespace Discord.Audio
break; break;
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
{ {
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer); var data = e.Payload.ToObject<VoiceServerUpdateEvent>(Serializer);
if (data.GuildId == VoiceSocket.Server?.Id) if (data.GuildId == VoiceSocket.Server?.Id)
{ {
var client = Service.Client; var client = Service.Client;
VoiceSocket.Token = data.Token; var id = client.CurrentUser?.Id;
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; if (id != null)
await VoiceSocket.Connect().ConfigureAwait(false); {
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false);
}
} }
} }
break; break;
@@ -233,15 +305,11 @@ namespace Discord.Audio
VoiceSocket.WaitForQueue(); VoiceSocket.WaitForQueue();
} }
private void SendVoiceUpdate() public void SendVoiceUpdate(ulong? serverId, ulong? channelId)
{ {
var serverId = VoiceSocket.Server?.Id; GatewaySocket.SendUpdateVoice(serverId, channelId,
if (serverId != null) (Service.Config.Mode | AudioMode.Outgoing) == 0,
{ (Service.Config.Mode | AudioMode.Incoming) == 0);
GatewaySocket.SendUpdateVoice(serverId, VoiceSocket.Channel?.Id,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
}
} }
} }
} }

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

View File

@@ -1,4 +1,4 @@
using Discord.Net.WebSockets; using Nito.AsyncEx;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; using System.Linq;
@@ -8,8 +8,10 @@ namespace Discord.Audio
{ {
public class AudioService : IService public class AudioService : IService
{ {
private AudioClient _defaultClient; private readonly AsyncLock _asyncLock;
private ConcurrentDictionary<ulong, IAudioClient> _voiceClients; private AudioClient _defaultClient; //Only used for single server
private VirtualClient _currentClient; //Only used for single server
private ConcurrentDictionary<ulong, AudioClient> _voiceClients;
private ConcurrentDictionary<User, bool> _talkingUsers; private ConcurrentDictionary<User, bool> _talkingUsers;
private int _nextClientId; private int _nextClientId;
@@ -30,18 +32,20 @@ namespace Discord.Audio
public AudioService(AudioServiceConfig config) public AudioService(AudioServiceConfig config)
{ {
Config = config; Config = config;
} _asyncLock = new AsyncLock();
}
void IService.Install(DiscordClient client) void IService.Install(DiscordClient client)
{ {
Client = client; Client = client;
Config.Lock(); Config.Lock();
if (Config.EnableMultiserver) if (Config.EnableMultiserver)
_voiceClients = new ConcurrentDictionary<ulong, IAudioClient>(); _voiceClients = new ConcurrentDictionary<ulong, AudioClient>();
else else
{ {
var logger = Client.Log.CreateLogger("Voice"); var logger = Client.Log.CreateLogger("Voice");
_defaultClient = new SimpleAudioClient(this, 0, logger); _defaultClient = new AudioClient(Client, null, 0);
} }
_talkingUsers = new ConcurrentDictionary<User, bool>(); _talkingUsers = new ConcurrentDictionary<User, bool>();
@@ -75,68 +79,30 @@ namespace Discord.Audio
{ {
if (server == null) throw new ArgumentNullException(nameof(server)); if (server == null) throw new ArgumentNullException(nameof(server));
if (!Config.EnableMultiserver) if (Config.EnableMultiserver)
{ {
if (server == _defaultClient.Server) AudioClient client;
return (_defaultClient as SimpleAudioClient).CurrentClient;
else
return null;
}
else
{
IAudioClient client;
if (_voiceClients.TryGetValue(server.Id, out client)) if (_voiceClients.TryGetValue(server.Id, out client))
return client; return client;
else else
return null; return null;
} }
} else
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); if (server == _currentClient.Server)
return _currentClient;
var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}"); else
var voiceLogger = Client.Log.CreateLogger($"Voice #{id}"); return null;
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 //Called from AudioClient.Disconnect
internal async Task RemoveClient(Server server, IAudioClient client) 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)) if (_voiceClients.TryUpdate(server.Id, null, client))
{ _voiceClients.TryRemove(server.Id, out client);
await client.Disconnect();
await (client as AudioClient).GatewaySocket.Disconnect();
}
} }
} }
@@ -144,16 +110,48 @@ namespace Discord.Audio
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
if (!Config.EnableMultiserver) var server = channel.Server;
using (await _asyncLock.LockAsync().ConfigureAwait(false))
{ {
await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false); if (Config.EnableMultiserver)
return _defaultClient; {
} AudioClient client;
else if (!_voiceClients.TryGetValue(server.Id, out client))
{ {
var client = await CreateClient(channel.Server).ConfigureAwait(false); client = new AudioClient(Client, server, unchecked(++_nextClientId));
await client.Join(channel).ConfigureAwait(false); _voiceClients[server.Id] = client;
return 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) if (Config.EnableMultiserver)
{ {
IAudioClient client; AudioClient client;
if (_voiceClients.TryRemove(server.Id, out client)) if (_voiceClients.TryRemove(server.Id, out client))
await client.Disconnect().ConfigureAwait(false); await client.Disconnect().ConfigureAwait(false);
} }
else else
{ {
IAudioClient client = GetClient(server); using (await _asyncLock.LockAsync().ConfigureAwait(false))
if (client != null) {
await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false); var client = GetClient(server) as VirtualClient;
if (client != null)
await _defaultClient.Disconnect().ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -19,7 +19,7 @@ using System.Threading.Tasks;
namespace Discord.Net.WebSockets namespace Discord.Net.WebSockets
{ {
public partial class VoiceWebSocket : WebSocket public partial class VoiceSocket : WebSocket
{ {
private const int MaxOpusSize = 4000; private const int MaxOpusSize = 4000;
private const string EncryptedMode = "xsalsa20_poly1305"; private const string EncryptedMode = "xsalsa20_poly1305";
@@ -27,8 +27,7 @@ namespace Discord.Net.WebSockets
private readonly int _targetAudioBufferLength; private readonly int _targetAudioBufferLength;
private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders; private readonly ConcurrentDictionary<uint, OpusDecoder> _decoders;
private readonly AudioClient _audioClient; private readonly AudioServiceConfig _audioConfig;
private readonly AudioServiceConfig _config;
private Task _sendTask, _receiveTask; private Task _sendTask, _receiveTask;
private VoiceBuffer _sendBuffer; private VoiceBuffer _sendBuffer;
private OpusEncoder _encoder; private OpusEncoder _encoder;
@@ -41,6 +40,8 @@ namespace Discord.Net.WebSockets
private ushort _sequence; private ushort _sequence;
private string _encryptionMode; private string _encryptionMode;
private int _ping; private int _ping;
private ulong? _userId;
private string _sessionId;
public string Token { get; internal set; } public string Token { get; internal set; }
public Server Server { get; internal set; } public Server Server { get; internal set; }
@@ -57,32 +58,37 @@ namespace Discord.Net.WebSockets
internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count)
=> FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count));
internal VoiceWebSocket(DiscordClient client, AudioClient audioClient, Logger logger) internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger)
: base(client, logger) : base(config, serializer, logger)
{ {
_audioClient = audioClient; _audioConfig = audioConfig;
_config = client.Audio().Config;
_decoders = new ConcurrentDictionary<uint, OpusDecoder>(); _decoders = new ConcurrentDictionary<uint, OpusDecoder>();
_targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames _targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize]; _encodingBuffer = new byte[MaxOpusSize];
_ssrcMapping = new ConcurrentDictionary<uint, ulong>(); _ssrcMapping = new ConcurrentDictionary<uint, ulong>();
_encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed); _encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed);
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
} }
public Task Connect() public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken)
=> BeginConnect(); {
Host = host;
Token = token;
_userId = userId;
_sessionId = sessionId;
return BeginConnect(parentCancelToken);
}
private async Task Reconnect() private async Task Reconnect()
{ {
try try
{ {
var cancelToken = ParentCancelToken.Value; var cancelToken = _parentCancelToken;
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
try try
{ {
await Connect().ConfigureAwait(false); await BeginConnect(_parentCancelToken).ConfigureAwait(false);
break; break;
} }
catch (OperationCanceledException) { throw; } catch (OperationCanceledException) { throw; }
@@ -90,31 +96,35 @@ namespace Discord.Net.WebSockets
{ {
Logger.Error("Reconnect failed", ex); Logger.Error("Reconnect failed", ex);
//Net is down? We can keep trying to reconnect until the user runs Disconnect() //Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
} }
} }
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }
} }
public Task Disconnect() => _taskManager.Stop(true); public async Task Disconnect()
{
await _taskManager.Stop(true).ConfigureAwait(false);
_userId = null;
}
protected override async Task Run() protected override async Task Run()
{ {
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
if (_config.Mode.HasFlag(AudioMode.Outgoing)) if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing))
_sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); _sendTask = Task.Run(() => SendVoiceAsync(CancelToken));
_receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); _receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken));
SendIdentify(); SendIdentify(_userId.Value, _sessionId);
#if !DOTNET5_4 #if !DOTNET5_4
tasks.Add(WatcherAsync()); tasks.Add(WatcherAsync());
#endif #endif
tasks.AddRange(_engine.GetTasks(CancelToken)); tasks.AddRange(_engine.GetTasks(CancelToken));
tasks.Add(HeartbeatAsync(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken));
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false);
} }
protected override async Task Cleanup() protected override async Task Cleanup()
{ {
@@ -148,7 +158,7 @@ namespace Discord.Net.WebSockets
int packetLength, resultOffset, resultLength; int packetLength, resultOffset, resultLength;
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
if ((_config.Mode & AudioMode.Incoming) != 0) if ((_audioConfig.Mode & AudioMode.Incoming) != 0)
{ {
decodingBuffer = new byte[MaxOpusSize]; decodingBuffer = new byte[MaxOpusSize];
nonce = new byte[24]; nonce = new byte[24];
@@ -184,7 +194,7 @@ namespace Discord.Net.WebSockets
int port = packet[68] | packet[69] << 8; int port = packet[68] | packet[69] << 8;
SendSelectProtocol(ip, port); SendSelectProtocol(ip, port);
if ((_config.Mode & AudioMode.Incoming) == 0) if ((_audioConfig.Mode & AudioMode.Incoming) == 0)
return; //We dont need this thread anymore return; //We dont need this thread anymore
} }
else else
@@ -395,7 +405,7 @@ namespace Discord.Net.WebSockets
var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(); var address = (await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault();
_endpoint = new IPEndPoint(address, payload.Port); _endpoint = new IPEndPoint(address, payload.Port);
if (_config.EnableEncryption) if (_audioConfig.EnableEncryption)
{ {
if (payload.Modes.Contains(EncryptedMode)) if (payload.Modes.Contains(EncryptedMode))
{ {
@@ -467,12 +477,12 @@ namespace Discord.Net.WebSockets
public override void SendHeartbeat() public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand()); => QueueMessage(new HeartbeatCommand());
public void SendIdentify() public void SendIdentify(ulong id, string sessionId)
=> QueueMessage(new IdentifyCommand => QueueMessage(new IdentifyCommand
{ {
GuildId = Server.Id, GuildId = Server.Id,
UserId = _client.CurrentUser.Id, UserId = id,
SessionId = _client.SessionId, SessionId = sessionId,
Token = Token Token = Token
}); });
public void SendSelectProtocol(string externalAddress, int externalPort) public void SendSelectProtocol(string externalAddress, int externalPort)

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

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

View File

@@ -5,7 +5,8 @@ using System.Threading.Tasks;
namespace Discord.Commands namespace Discord.Commands
{ {
public sealed class Command //TODO: Make this more friendly and expose it to be extendable
public class Command
{ {
private string[] _aliases; private string[] _aliases;
internal CommandParameter[] _parameters; internal CommandParameter[] _parameters;

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
namespace Discord.Commands namespace Discord.Commands
{ {
//TODO: Make this more friendly and expose it to be extendable
public sealed class CommandBuilder public sealed class CommandBuilder
{ {
private readonly CommandService _service; private readonly CommandService _service;
@@ -18,17 +19,20 @@ namespace Discord.Commands
public CommandService Service => _service; public CommandService Service => _service;
internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null)
{ {
_service = service; _service = service;
_command = command; _prefix = prefix;
_command.Category = category;
_params = new List<CommandParameter>(); _command = new Command(AppendPrefix(prefix, text));
_command.Category = category;
if (initialChecks != null) if (initialChecks != null)
_checks = new List<IPermissionChecker>(initialChecks); _checks = new List<IPermissionChecker>(initialChecks);
else else
_checks = new List<IPermissionChecker>(); _checks = new List<IPermissionChecker>();
_prefix = prefix;
_params = new List<CommandParameter>();
_aliases = new List<string>(); _aliases = new List<string>();
_allowRequiredParams = true; _allowRequiredParams = true;
@@ -112,7 +116,7 @@ namespace Discord.Commands
return prefix; return prefix;
} }
} }
public sealed class CommandGroupBuilder public class CommandGroupBuilder
{ {
private readonly CommandService _service; private readonly CommandService _service;
private readonly string _prefix; private readonly string _prefix;
@@ -154,9 +158,6 @@ namespace Discord.Commands
public CommandBuilder CreateCommand() public CommandBuilder CreateCommand()
=> CreateCommand(""); => CreateCommand("");
public CommandBuilder CreateCommand(string cmd) public CommandBuilder CreateCommand(string cmd)
{ => new CommandBuilder(_service, cmd, _prefix, _category, _checks);
var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd));
return new CommandBuilder(_service, command, _prefix, _category, _checks);
}
} }
} }

View File

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

View File

@@ -7,7 +7,7 @@ using System.Linq;
namespace Discord.Modules namespace Discord.Modules
{ {
public sealed class ModuleManager public class ModuleManager
{ {
public event EventHandler<ServerEventArgs> ServerEnabled = delegate { }; public event EventHandler<ServerEventArgs> ServerEnabled = delegate { };
public event EventHandler<ServerEventArgs> ServerDisabled = delegate { }; public event EventHandler<ServerEventArgs> ServerDisabled = delegate { };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ namespace Discord.API.Client
{ {
public class Message : MessageReference public class Message : MessageReference
{ {
public sealed class Attachment public class Attachment
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
@@ -23,9 +23,9 @@ namespace Discord.API.Client
public int Height { get; set; } public int Height { get; set; }
} }
public sealed class Embed public class Embed
{ {
public sealed class Reference public class Reference
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
@@ -33,7 +33,7 @@ namespace Discord.API.Client
public string Name { get; set; } public string Name { get; set; }
} }
public sealed class ThumbnailInfo public class ThumbnailInfo
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }
@@ -44,7 +44,7 @@ namespace Discord.API.Client
[JsonProperty("height")] [JsonProperty("height")]
public int Height { get; set; } public int Height { get; set; }
} }
public sealed class VideoInfo public class VideoInfo
{ {
[JsonProperty("url")] [JsonProperty("url")]
public string Url { get; set; } public string Url { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildMembersChunkEvent public class GuildMembersChunkEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildRoleCreateEvent public class GuildRoleCreateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }

View File

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

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class GuildRoleUpdateEvent public class GuildRoleUpdateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class TypingStartEvent public class TypingStartEvent
{ {
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
public ulong UserId { get; set; } public ulong UserId { get; set; }

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.GatewaySocket namespace Discord.API.Client.GatewaySocket
{ {
public sealed class VoiceServerUpdateEvent public class VoiceServerUpdateEvent
{ {
[JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))]
public ulong GuildId { get; set; } public ulong GuildId { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class GetWidgetRequest : IRestRequest<GetWidgetResponse> public class GetWidgetRequest : IRestRequest<GetWidgetResponse>
{ {
string IRestRequest.Method => "GET"; string IRestRequest.Method => "GET";
string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json";
@@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest
} }
} }
public sealed class GetWidgetResponse public class GetWidgetResponse
{ {
public sealed class Channel public class Channel
{ {
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; } public ulong Id { get; set; }
@@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("position")] [JsonProperty("position")]
public int Position { get; set; } public int Position { get; set; }
} }
public sealed class User : UserReference public class User : UserReference
{ {
[JsonProperty("avatar_url")] [JsonProperty("avatar_url")]
public string AvatarUrl { get; set; } public string AvatarUrl { get; set; }
@@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest
[JsonProperty("game")] [JsonProperty("game")]
public UserGame Game { get; set; } public UserGame Game { get; set; }
} }
public sealed class UserGame public class UserGame
{ {
[JsonProperty("id")] [JsonProperty("id")]
public int Id { get; set; } public int Id { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class PruneMembersRequest : IRestRequest<PruneMembersResponse> public class PruneMembersRequest : IRestRequest<PruneMembersResponse>
{ {
string IRestRequest.Method => IsSimulation ? "GET" : "POST"; string IRestRequest.Method => IsSimulation ? "GET" : "POST";
string IRestRequest.Endpoint => $"guilds/{GuildId}/prune?days={Days}"; 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")] [JsonProperty("pruned")]
public int Pruned { get; set; } public int Pruned { get; set; }

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ using System.Linq;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class ReorderChannelsRequest : IRestRequest public class ReorderChannelsRequest : IRestRequest
{ {
string IRestRequest.Method => "PATCH"; string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; string IRestRequest.Endpoint => $"guilds/{GuildId}/channels";
@@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest
} }
bool IRestRequest.IsPrivate => false; bool IRestRequest.IsPrivate => false;
public sealed class Channel public class Channel
{ {
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; } public ulong Id { get; set; }

View File

@@ -5,7 +5,7 @@ using System.Linq;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class ReorderRolesRequest : IRestRequest<Role[]> public class ReorderRolesRequest : IRestRequest<Role[]>
{ {
string IRestRequest.Method => "PATCH"; string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; string IRestRequest.Endpoint => $"guilds/{GuildId}/roles";
@@ -19,7 +19,7 @@ namespace Discord.API.Client.Rest
} }
bool IRestRequest.IsPrivate => false; bool IRestRequest.IsPrivate => false;
public sealed class Role public class Role
{ {
[JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))]
public ulong Id { get; set; } public ulong Id { get; set; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Discord.API.Client.Rest namespace Discord.API.Client.Rest
{ {
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateMemberRequest : IRestRequest public class UpdateMemberRequest : IRestRequest
{ {
string IRestRequest.Method => "PATCH"; string IRestRequest.Method => "PATCH";
string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}";

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
namespace Discord.API.Client.VoiceSocket namespace Discord.API.Client.VoiceSocket
{ {
public sealed class HeartbeatCommand : IWebSocketMessage public class HeartbeatCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat;
object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); object IWebSocketMessage.Payload => EpochTime.GetMilliseconds();

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.VoiceSocket namespace Discord.API.Client.VoiceSocket
{ {
public sealed class IdentifyCommand : IWebSocketMessage public class IdentifyCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Identify; int IWebSocketMessage.OpCode => (int)OpCodes.Identify;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;

View File

@@ -2,13 +2,13 @@
namespace Discord.API.Client.VoiceSocket namespace Discord.API.Client.VoiceSocket
{ {
public sealed class SelectProtocolCommand : IWebSocketMessage public class SelectProtocolCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.SelectProtocol; int IWebSocketMessage.OpCode => (int)OpCodes.SelectProtocol;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false; bool IWebSocketMessage.IsPrivate => false;
public sealed class Data public class Data
{ {
[JsonProperty("address")] [JsonProperty("address")]
public string Address { get; set; } public string Address { get; set; }

View File

@@ -2,7 +2,7 @@
namespace Discord.API.Client.VoiceSocket namespace Discord.API.Client.VoiceSocket
{ {
public sealed class SetSpeakingCommand : IWebSocketMessage public class SetSpeakingCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.Speaking; int IWebSocketMessage.OpCode => (int)OpCodes.Speaking;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace Discord.API.Client.VoiceSocket namespace Discord.API.Client.VoiceSocket
{ {
public sealed class SpeakingEvent public class SpeakingEvent
{ {
[JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))]
public ulong UserId { get; set; } public ulong UserId { get; set; }

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Discord.API.Converters namespace Discord.API.Converters
{ {
public sealed class LongStringConverter : JsonConverter public class LongStringConverter : JsonConverter
{ {
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
=> objectType == typeof(ulong); => objectType == typeof(ulong);
@@ -14,7 +14,7 @@ namespace Discord.API.Converters
=> writer.WriteValue(((ulong)value).ToIdString()); => writer.WriteValue(((ulong)value).ToIdString());
} }
public sealed class NullableLongStringConverter : JsonConverter public class NullableLongStringConverter : JsonConverter
{ {
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
=> objectType == typeof(ulong?); => objectType == typeof(ulong?);
@@ -24,7 +24,7 @@ namespace Discord.API.Converters
=> writer.WriteValue(((ulong?)value).ToIdString()); => 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 bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong>);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 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 bool CanConvert(Type objectType) => objectType == typeof(IEnumerable<ulong[]>);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)

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