| @@ -62,8 +62,8 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> | <Compile Include="..\Discord.Net.Audio\InternalIsSpeakingEventArgs.cs"> | ||||
| <Link>InternalIsSpeakingEventArgs.cs</Link> | <Link>InternalIsSpeakingEventArgs.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Net\VoiceWebSocket.cs"> | |||||
| <Link>Net\VoiceWebSocket.cs</Link> | |||||
| <Compile Include="..\Discord.Net.Audio\Net\VoiceSocket.cs"> | |||||
| <Link>Net\VoiceSocket.cs</Link> | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusConverter.cs"> | ||||
| <Link>Opus\OpusConverter.cs</Link> | <Link>Opus\OpusConverter.cs</Link> | ||||
| @@ -74,15 +74,15 @@ | |||||
| <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | <Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs"> | ||||
| <Link>Opus\OpusEncoder.cs</Link> | <Link>Opus\OpusEncoder.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\SimpleAudioClient.cs"> | |||||
| <Link>SimpleAudioClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | <Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs"> | ||||
| <Link>Sodium\SecretBox.cs</Link> | <Link>Sodium\SecretBox.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | <Compile Include="..\Discord.Net.Audio\UserIsTalkingEventArgs.cs"> | ||||
| <Link>UserIsTalkingEventArgs.cs</Link> | <Link>UserIsTalkingEventArgs.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net.Audio\VirtualClient.cs"> | |||||
| <Link>VirtualClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | <Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs"> | ||||
| <Link>VoiceBuffer.cs</Link> | <Link>VoiceBuffer.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -1,9 +1,12 @@ | |||||
| using Discord.API.Client.GatewaySocket; | using Discord.API.Client.GatewaySocket; | ||||
| using Discord.API.Client.Rest; | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Rest; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Nito.AsyncEx; | using Nito.AsyncEx; | ||||
| using System; | using System; | ||||
| using System.Diagnostics; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -41,124 +44,190 @@ namespace Discord.Audio | |||||
| } | } | ||||
| } | } | ||||
| private readonly DiscordConfig _config; | |||||
| private readonly AsyncLock _connectionLock; | private readonly AsyncLock _connectionLock; | ||||
| private readonly JsonSerializer _serializer; | |||||
| private CancellationTokenSource _cancelTokenSource; | |||||
| private readonly TaskManager _taskManager; | |||||
| private ConnectionState _gatewayState; | |||||
| internal AudioService Service { get; } | |||||
| internal Logger Logger { get; } | internal Logger Logger { get; } | ||||
| /// <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; | |||||
| _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.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)) | using (await _connectionLock.LockAsync().ConfigureAwait(false)) | ||||
| { | |||||
| await Service.RemoveClient(VoiceSocket.Server, this).ConfigureAwait(false); | |||||
| VoiceSocket.Channel = null; | |||||
| SendVoiceUpdate(); | |||||
| await VoiceSocket.Disconnect(); | |||||
| } | |||||
| await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)); | |||||
| } | } | ||||
| private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) | |||||
| private async void OnReceivedEvent(WebSocketEventEventArgs e) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| @@ -166,11 +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; | |||||
| VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||||
| await VoiceSocket.Connect().ConfigureAwait(false); | |||||
| var id = client.CurrentUser?.Id; | |||||
| if (id != null) | |||||
| { | |||||
| var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; | |||||
| await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -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; | |||||
| 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); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.Net.WebSockets; | |||||
| using Nito.AsyncEx; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -8,8 +8,10 @@ namespace Discord.Audio | |||||
| { | { | ||||
| public class AudioService : IService | public class AudioService : IService | ||||
| { | { | ||||
| private AudioClient _defaultClient; | |||||
| private ConcurrentDictionary<ulong, IAudioClient> _voiceClients; | |||||
| private readonly AsyncLock _asyncLock; | |||||
| private AudioClient _defaultClient; //Only used for single server | |||||
| private VirtualClient _currentClient; //Only used for single server | |||||
| private ConcurrentDictionary<ulong, AudioClient> _voiceClients; | |||||
| private ConcurrentDictionary<User, bool> _talkingUsers; | private ConcurrentDictionary<User, bool> _talkingUsers; | ||||
| private int _nextClientId; | private int _nextClientId; | ||||
| @@ -30,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) | |||||
| return (_defaultClient as SimpleAudioClient).CurrentClient; | |||||
| AudioClient client; | |||||
| if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
| return client; | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| IAudioClient client; | |||||
| if (_voiceClients.TryGetValue(server.Id, out client)) | |||||
| return client; | |||||
| if (server == _currentClient.Server) | |||||
| return _currentClient; | |||||
| else | else | ||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| private async Task<IAudioClient> CreateClient(Server server) | |||||
| { | |||||
| var client = _voiceClients.GetOrAdd(server.Id, _ => null); //Placeholder, so we can't have two clients connecting at once | |||||
| if (client == null) | |||||
| { | |||||
| int id = unchecked(++_nextClientId); | |||||
| var gatewayLogger = Client.Log.CreateLogger($"Gateway #{id}"); | |||||
| var voiceLogger = Client.Log.CreateLogger($"Voice #{id}"); | |||||
| var gatewaySocket = new GatewaySocket(Client, gatewayLogger); | |||||
| var voiceClient = new AudioClient(this, id, server, Client.GatewaySocket, voiceLogger); | |||||
| await voiceClient.Connect(true).ConfigureAwait(false); | |||||
| /*voiceClient.VoiceSocket.FrameReceived += (s, e) => | |||||
| { | |||||
| OnFrameReceieved(e); | |||||
| }; | |||||
| voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => | |||||
| { | |||||
| var user = server.GetUser(e.UserId); | |||||
| OnUserIsSpeakingUpdated(user, e.IsSpeaking); | |||||
| };*/ | |||||
| //Update the placeholder only it still exists (RemoveClient wasnt called) | |||||
| if (!_voiceClients.TryUpdate(server.Id, voiceClient, null)) | |||||
| { | |||||
| //If it was, cleanup | |||||
| await voiceClient.Disconnect().ConfigureAwait(false); ; | |||||
| await gatewaySocket.Disconnect().ConfigureAwait(false); ; | |||||
| } | |||||
| } | |||||
| return client; | |||||
| } | |||||
| //TODO: This isn't threadsafe | |||||
| internal async Task RemoveClient(Server server, IAudioClient client) | |||||
| //Called from AudioClient.Disconnect | |||||
| internal async Task RemoveClient(Server server, AudioClient client) | |||||
| { | { | ||||
| if (Config.EnableMultiserver && server != null) | |||||
| using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
| { | { | ||||
| if (_voiceClients.TryRemove(server.Id, out client)) | |||||
| { | |||||
| await client.Disconnect(); | |||||
| await (client as AudioClient).GatewaySocket.Disconnect(); | |||||
| } | |||||
| if (_voiceClients.TryUpdate(server.Id, null, client)) | |||||
| _voiceClients.TryRemove(server.Id, out client); | |||||
| } | } | ||||
| } | } | ||||
| @@ -144,16 +110,48 @@ namespace Discord.Audio | |||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| if (!Config.EnableMultiserver) | |||||
| { | |||||
| await (_defaultClient as SimpleAudioClient).Connect(channel, false).ConfigureAwait(false); | |||||
| return _defaultClient; | |||||
| } | |||||
| else | |||||
| var server = channel.Server; | |||||
| using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
| { | { | ||||
| var client = await CreateClient(channel.Server).ConfigureAwait(false); | |||||
| await client.Join(channel).ConfigureAwait(false); | |||||
| return client; | |||||
| if (Config.EnableMultiserver) | |||||
| { | |||||
| AudioClient client; | |||||
| if (!_voiceClients.TryGetValue(server.Id, out client)) | |||||
| { | |||||
| client = new AudioClient(Client, server, unchecked(++_nextClientId)); | |||||
| _voiceClients[server.Id] = client; | |||||
| await client.Connect().ConfigureAwait(false); | |||||
| /*voiceClient.VoiceSocket.FrameReceived += (s, e) => | |||||
| { | |||||
| OnFrameReceieved(e); | |||||
| }; | |||||
| voiceClient.VoiceSocket.UserIsSpeaking += (s, e) => | |||||
| { | |||||
| var user = server.GetUser(e.UserId); | |||||
| OnUserIsSpeakingUpdated(user, e.IsSpeaking); | |||||
| };*/ | |||||
| } | |||||
| await client.Join(channel).ConfigureAwait(false); | |||||
| return client; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (_defaultClient.Server != server) | |||||
| { | |||||
| await _defaultClient.Disconnect(); | |||||
| _defaultClient.VoiceSocket.Server = server; | |||||
| await _defaultClient.Connect().ConfigureAwait(false); | |||||
| } | |||||
| var client = new VirtualClient(_defaultClient, server); | |||||
| _currentClient = client; | |||||
| await client.Join(channel).ConfigureAwait(false); | |||||
| return client; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -163,15 +161,18 @@ namespace Discord.Audio | |||||
| if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
| { | { | ||||
| IAudioClient client; | |||||
| AudioClient client; | |||||
| if (_voiceClients.TryRemove(server.Id, out client)) | if (_voiceClients.TryRemove(server.Id, out client)) | ||||
| await client.Disconnect().ConfigureAwait(false); | await client.Disconnect().ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| IAudioClient client = GetClient(server); | |||||
| if (client != null) | |||||
| await (_defaultClient as SimpleAudioClient).Leave(client as SimpleAudioClient.VirtualClient).ConfigureAwait(false); | |||||
| using (await _asyncLock.LockAsync().ConfigureAwait(false)) | |||||
| { | |||||
| var client = GetClient(server) as VirtualClient; | |||||
| if (client != null) | |||||
| await _defaultClient.Disconnect().ConfigureAwait(false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -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 _config; | |||||
| private readonly AudioServiceConfig _audioConfig; | |||||
| private Task _sendTask, _receiveTask; | private Task _sendTask, _receiveTask; | ||||
| private VoiceBuffer _sendBuffer; | private VoiceBuffer _sendBuffer; | ||||
| private OpusEncoder _encoder; | private OpusEncoder _encoder; | ||||
| @@ -41,6 +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) | |||||
| : base(client, logger) | |||||
| internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger) | |||||
| : base(config, serializer, logger) | |||||
| { | { | ||||
| _audioClient = audioClient; | |||||
| _config = client.Audio().Config; | |||||
| _audioConfig = audioConfig; | |||||
| _decoders = new ConcurrentDictionary<uint, OpusDecoder>(); | _decoders = new ConcurrentDictionary<uint, OpusDecoder>(); | ||||
| _targetAudioBufferLength = _config.BufferLength / 20; //20 ms frames | |||||
| _targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames | |||||
| _encodingBuffer = new byte[MaxOpusSize]; | _encodingBuffer = new byte[MaxOpusSize]; | ||||
| _ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | _ssrcMapping = new ConcurrentDictionary<uint, ulong>(); | ||||
| _encoder = new OpusEncoder(48000, _config.Channels, 20, _config.Bitrate, OpusApplication.MusicOrMixed); | |||||
| _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | |||||
| _encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed); | |||||
| _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); | |||||
| } | } | ||||
| public Task Connect() | |||||
| => BeginConnect(); | |||||
| public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken) | |||||
| { | |||||
| Host = host; | |||||
| Token = token; | |||||
| _userId = userId; | |||||
| _sessionId = sessionId; | |||||
| return BeginConnect(parentCancelToken); | |||||
| } | |||||
| private async Task Reconnect() | private async Task Reconnect() | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| var cancelToken = ParentCancelToken.Value; | |||||
| await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| var cancelToken = _parentCancelToken; | |||||
| await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); | |||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await Connect().ConfigureAwait(false); | |||||
| await BeginConnect(_parentCancelToken).ConfigureAwait(false); | |||||
| break; | break; | ||||
| } | } | ||||
| catch (OperationCanceledException) { throw; } | catch (OperationCanceledException) { throw; } | ||||
| @@ -90,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, | |||||
| SessionId = _client.SessionId, | |||||
| UserId = id, | |||||
| SessionId = sessionId, | |||||
| Token = Token | Token = Token | ||||
| }); | }); | ||||
| public void SendSelectProtocol(string externalAddress, int externalPort) | public void SendSelectProtocol(string externalAddress, int externalPort) | ||||
| @@ -1,72 +0,0 @@ | |||||
| using Discord.Logging; | |||||
| using Nito.AsyncEx; | |||||
| using System.IO; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| internal class SimpleAudioClient : AudioClient | |||||
| { | |||||
| internal class VirtualClient : IAudioClient | |||||
| { | |||||
| private readonly SimpleAudioClient _client; | |||||
| ConnectionState IAudioClient.State => _client.VoiceSocket.State; | |||||
| Server IAudioClient.Server => _client.VoiceSocket.Server; | |||||
| Channel IAudioClient.Channel => _client.VoiceSocket.Channel; | |||||
| Stream IAudioClient.OutputStream => _client.OutputStream; | |||||
| public VirtualClient(SimpleAudioClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| Task IAudioClient.Disconnect() => _client.Leave(this); | |||||
| Task IAudioClient.Join(Channel channel) => _client.Join(channel); | |||||
| void IAudioClient.Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); | |||||
| void IAudioClient.Clear() => _client.Clear(); | |||||
| void IAudioClient.Wait() => _client.Wait(); | |||||
| } | |||||
| private readonly AsyncLock _connectionLock; | |||||
| internal VirtualClient CurrentClient { get; private set; } | |||||
| public SimpleAudioClient(AudioService service, int id, Logger logger) | |||||
| : base(service, id, null, service.Client.GatewaySocket, logger) | |||||
| { | |||||
| _connectionLock = new AsyncLock(); | |||||
| } | |||||
| //Only disconnects if is current a member of this server | |||||
| public async Task Leave(VirtualClient client) | |||||
| { | |||||
| using (await _connectionLock.LockAsync().ConfigureAwait(false)) | |||||
| { | |||||
| if (CurrentClient == client) | |||||
| { | |||||
| CurrentClient = null; | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| internal async Task<IAudioClient> Connect(Channel channel, bool connectGateway) | |||||
| { | |||||
| using (await _connectionLock.LockAsync().ConfigureAwait(false)) | |||||
| { | |||||
| bool changeServer = channel.Server != VoiceSocket.Server; | |||||
| if (changeServer || CurrentClient == null) | |||||
| { | |||||
| await Disconnect().ConfigureAwait(false); | |||||
| CurrentClient = new VirtualClient(this); | |||||
| VoiceSocket.Server = channel.Server; | |||||
| await Connect(connectGateway).ConfigureAwait(false); | |||||
| } | |||||
| await Join(channel).ConfigureAwait(false); | |||||
| return CurrentClient; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,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(); | |||||
| } | |||||
| } | |||||
| @@ -5,7 +5,8 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| public sealed class Command | |||||
| //TODO: Make this more friendly and expose it to be extendable | |||||
| public class Command | |||||
| { | { | ||||
| private string[] _aliases; | private string[] _aliases; | ||||
| internal CommandParameter[] _parameters; | internal CommandParameter[] _parameters; | ||||
| @@ -6,6 +6,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| //TODO: Make this more friendly and expose it to be extendable | |||||
| public sealed class CommandBuilder | public sealed class CommandBuilder | ||||
| { | { | ||||
| private readonly CommandService _service; | private readonly CommandService _service; | ||||
| @@ -18,17 +19,20 @@ namespace Discord.Commands | |||||
| public CommandService Service => _service; | public CommandService Service => _service; | ||||
| internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) | |||||
| internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) | |||||
| { | { | ||||
| _service = service; | |||||
| _command = command; | |||||
| _command.Category = category; | |||||
| _params = new List<CommandParameter>(); | |||||
| _service = service; | |||||
| _prefix = prefix; | |||||
| _command = new Command(AppendPrefix(prefix, text)); | |||||
| _command.Category = category; | |||||
| if (initialChecks != null) | if (initialChecks != null) | ||||
| _checks = new List<IPermissionChecker>(initialChecks); | _checks = new List<IPermissionChecker>(initialChecks); | ||||
| else | else | ||||
| _checks = new List<IPermissionChecker>(); | _checks = new List<IPermissionChecker>(); | ||||
| _prefix = prefix; | |||||
| _params = new List<CommandParameter>(); | |||||
| _aliases = new List<string>(); | _aliases = new List<string>(); | ||||
| _allowRequiredParams = true; | _allowRequiredParams = true; | ||||
| @@ -112,7 +116,7 @@ namespace Discord.Commands | |||||
| return prefix; | return prefix; | ||||
| } | } | ||||
| } | } | ||||
| public sealed class CommandGroupBuilder | |||||
| public class CommandGroupBuilder | |||||
| { | { | ||||
| private readonly CommandService _service; | private readonly CommandService _service; | ||||
| private readonly string _prefix; | private readonly string _prefix; | ||||
| @@ -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) | ||||
| { | |||||
| var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | |||||
| return new CommandBuilder(_service, command, _prefix, _category, _checks); | |||||
| } | |||||
| => new CommandBuilder(_service, cmd, _prefix, _category, _checks); | |||||
| } | } | ||||
| } | } | ||||
| @@ -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; | ||||
| @@ -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 { }; | ||||
| @@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class Channel : ChannelReference | public class Channel : ChannelReference | ||||
| { | { | ||||
| public sealed class PermissionOverwrite | |||||
| public class PermissionOverwrite | |||||
| { | { | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { get; set; } | public string Type { get; set; } | ||||
| @@ -4,7 +4,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class ExtendedGuild : Guild | public class ExtendedGuild : Guild | ||||
| { | { | ||||
| public sealed class ExtendedMemberInfo : Member | |||||
| public class ExtendedMemberInfo : Member | |||||
| { | { | ||||
| [JsonProperty("mute")] | [JsonProperty("mute")] | ||||
| public bool? IsServerMuted { get; set; } | public bool? IsServerMuted { get; set; } | ||||
| @@ -6,7 +6,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class Guild : GuildReference | public class Guild : GuildReference | ||||
| { | { | ||||
| public sealed class EmojiData | |||||
| public class EmojiData | |||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public string Id { get; set; } | public string Id { get; set; } | ||||
| @@ -4,7 +4,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class InviteReference | public class InviteReference | ||||
| { | { | ||||
| public sealed class GuildData : GuildReference | |||||
| public class GuildData : GuildReference | |||||
| { | { | ||||
| [JsonProperty("splash_hash")] | [JsonProperty("splash_hash")] | ||||
| public string Splash { get; set; } | public string Splash { get; set; } | ||||
| @@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class MemberPresence : MemberReference | public class MemberPresence : MemberReference | ||||
| { | { | ||||
| public sealed class GameInfo | |||||
| public class GameInfo | |||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| @@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class Message : MessageReference | public class Message : MessageReference | ||||
| { | { | ||||
| public sealed class Attachment | |||||
| public class Attachment | |||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public string Id { get; set; } | public string Id { get; set; } | ||||
| @@ -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; } | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class HeartbeatCommand : IWebSocketMessage | |||||
| public class HeartbeatCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; | int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; | ||||
| object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); | object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); | ||||
| @@ -4,7 +4,7 @@ using System.Collections.Generic; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class IdentifyCommand : IWebSocketMessage | |||||
| public class IdentifyCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | int IWebSocketMessage.OpCode => (int)OpCodes.Identify; | ||||
| object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class RequestMembersCommand : IWebSocketMessage | |||||
| public class RequestMembersCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; | ||||
| object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class ResumeCommand : IWebSocketMessage | |||||
| public class ResumeCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.Resume; | int IWebSocketMessage.OpCode => (int)OpCodes.Resume; | ||||
| object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
| @@ -3,13 +3,13 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class UpdateStatusCommand : IWebSocketMessage | |||||
| public class UpdateStatusCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; | int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; | ||||
| object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
| bool IWebSocketMessage.IsPrivate => false; | bool IWebSocketMessage.IsPrivate => false; | ||||
| public sealed class GameInfo | |||||
| public class GameInfo | |||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class UpdateVoiceCommand : IWebSocketMessage | |||||
| public class UpdateVoiceCommand : IWebSocketMessage | |||||
| { | { | ||||
| int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; | int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; | ||||
| object IWebSocketMessage.Payload => this; | object IWebSocketMessage.Payload => this; | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class ChannelCreateEvent : Channel { } | |||||
| public class ChannelCreateEvent : Channel { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class ChannelDeleteEvent : Channel { } | |||||
| public class ChannelDeleteEvent : Channel { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class ChannelUpdateEvent : Channel { } | |||||
| public class ChannelUpdateEvent : Channel { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildBanAddEvent : MemberReference { } | |||||
| public class GuildBanAddEvent : MemberReference { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildBanRemoveEvent : MemberReference { } | |||||
| public class GuildBanRemoveEvent : MemberReference { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildCreateEvent : ExtendedGuild { } | |||||
| public class GuildCreateEvent : ExtendedGuild { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildDeleteEvent : ExtendedGuild { } | |||||
| public class GuildDeleteEvent : ExtendedGuild { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket.Events | namespace Discord.API.Client.GatewaySocket.Events | ||||
| { | { | ||||
| //public sealed class GuildEmojisUpdateEvent { } | |||||
| //public class GuildEmojisUpdateEvent { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| //public sealed class GuildIntegrationsUpdateEvent { } | |||||
| //public class GuildIntegrationsUpdateEvent { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildMemberAddEvent : Member { } | |||||
| public class GuildMemberAddEvent : Member { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildMemberRemoveEvent : Member { } | |||||
| public class GuildMemberRemoveEvent : Member { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildMemberUpdateEvent : Member { } | |||||
| public class GuildMemberUpdateEvent : Member { } | |||||
| } | } | ||||
| @@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildMembersChunkEvent | |||||
| public class GuildMembersChunkEvent | |||||
| { | { | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
| @@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildRoleCreateEvent | |||||
| public class GuildRoleCreateEvent | |||||
| { | { | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildRoleDeleteEvent : RoleReference { } | |||||
| public class GuildRoleDeleteEvent : RoleReference { } | |||||
| } | } | ||||
| @@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildRoleUpdateEvent | |||||
| public class GuildRoleUpdateEvent | |||||
| { | { | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class GuildUpdateEvent : Guild { } | |||||
| public class GuildUpdateEvent : Guild { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class MessageAckEvent : MessageReference { } | |||||
| public class MessageAckEvent : MessageReference { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class MessageCreateEvent : Message { } | |||||
| public class MessageCreateEvent : Message { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class MessageDeleteEvent : MessageReference { } | |||||
| public class MessageDeleteEvent : MessageReference { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class MessageUpdateEvent : Message { } | |||||
| public class MessageUpdateEvent : Message { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class PresenceUpdateEvent : MemberPresence { } | |||||
| public class PresenceUpdateEvent : MemberPresence { } | |||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class ReadyEvent | |||||
| public class ReadyEvent | |||||
| { | { | ||||
| public sealed class ReadState | |||||
| public class ReadState | |||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public string ChannelId { get; set; } | public string ChannelId { get; set; } | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class RedirectEvent | |||||
| public class RedirectEvent | |||||
| { | { | ||||
| [JsonProperty("url")] | [JsonProperty("url")] | ||||
| public string Url { get; set; } | public string Url { get; set; } | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class ResumedEvent | |||||
| public class ResumedEvent | |||||
| { | { | ||||
| [JsonProperty("heartbeat_interval")] | [JsonProperty("heartbeat_interval")] | ||||
| public int HeartbeatInterval { get; set; } | public int HeartbeatInterval { get; set; } | ||||
| @@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class TypingStartEvent | |||||
| public class TypingStartEvent | |||||
| { | { | ||||
| [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong UserId { get; set; } | public ulong UserId { get; set; } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| //public sealed class UserSettingsUpdateEvent { } | |||||
| //public class UserSettingsUpdateEvent { } | |||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class UserUpdateEvent : User { } | |||||
| public class UserUpdateEvent : User { } | |||||
| } | } | ||||
| @@ -3,7 +3,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class VoiceServerUpdateEvent | |||||
| public class VoiceServerUpdateEvent | |||||
| { | { | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong GuildId { get; set; } | public ulong GuildId { get; set; } | ||||
| @@ -1,4 +1,4 @@ | |||||
| namespace Discord.API.Client.GatewaySocket | namespace Discord.API.Client.GatewaySocket | ||||
| { | { | ||||
| public sealed class VoiceStateUpdateEvent : MemberVoiceState { } | |||||
| public class VoiceStateUpdateEvent : MemberVoiceState { } | |||||
| } | } | ||||
| @@ -8,7 +8,7 @@ namespace Discord.API.Client | |||||
| object Payload { get; } | object Payload { get; } | ||||
| bool IsPrivate { get; } | bool IsPrivate { get; } | ||||
| } | } | ||||
| public sealed class WebSocketMessage | |||||
| public class WebSocketMessage | |||||
| { | { | ||||
| [JsonProperty("op")] | [JsonProperty("op")] | ||||
| public int? Operation { get; set; } | public int? Operation { get; set; } | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class AcceptInviteRequest : IRestRequest<InviteReference> | |||||
| public class AcceptInviteRequest : IRestRequest<InviteReference> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"invite/{InviteId}"; | string IRestRequest.Endpoint => $"invite/{InviteId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class AckMessageRequest : IRestRequest | |||||
| public class AckMessageRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; | ||||
| @@ -4,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}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class AddGuildBanRequest : IRestRequest | |||||
| public class AddGuildBanRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "PUT"; | string IRestRequest.Method => "PUT"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class CreateChannelRequest : IRestRequest<Channel> | |||||
| public class CreateChannelRequest : IRestRequest<Channel> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class CreateGuildRequest : IRestRequest<Guild> | |||||
| public class CreateGuildRequest : IRestRequest<Guild> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"guilds"; | string IRestRequest.Endpoint => $"guilds"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class CreateInviteRequest : IRestRequest<Invite> | |||||
| public class CreateInviteRequest : IRestRequest<Invite> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; | ||||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class CreatePrivateChannelRequest : IRestRequest<Channel> | |||||
| public class CreatePrivateChannelRequest : IRestRequest<Channel> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"users/@me/channels"; | string IRestRequest.Endpoint => $"users/@me/channels"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class CreateRoleRequest : IRestRequest<Role> | |||||
| public class CreateRoleRequest : IRestRequest<Role> | |||||
| { | { | ||||
| string IRestRequest.Method => "POST"; | string IRestRequest.Method => "POST"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class DeleteChannelRequest : IRestRequest<Channel> | |||||
| public class DeleteChannelRequest : IRestRequest<Channel> | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"channels/{ChannelId}"; | string IRestRequest.Endpoint => $"channels/{ChannelId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class DeleteInviteRequest : IRestRequest<Invite> | |||||
| public class DeleteInviteRequest : IRestRequest<Invite> | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"invite/{InviteCode}"; | string IRestRequest.Endpoint => $"invite/{InviteCode}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class DeleteMessageRequest : IRestRequest | |||||
| public class DeleteMessageRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; | string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class DeleteRoleRequest : IRestRequest | |||||
| public class DeleteRoleRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GatewayRequest : IRestRequest<GatewayResponse> | |||||
| public class GatewayRequest : IRestRequest<GatewayResponse> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"gateway"; | string IRestRequest.Endpoint => $"gateway"; | ||||
| @@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest | |||||
| bool IRestRequest.IsPrivate => false; | bool IRestRequest.IsPrivate => false; | ||||
| } | } | ||||
| public sealed class GatewayResponse | |||||
| public class GatewayResponse | |||||
| { | { | ||||
| [JsonProperty("url")] | [JsonProperty("url")] | ||||
| public string Url { get; set; } | public string Url { get; set; } | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetBansRequest : IRestRequest<UserReference[]> | |||||
| public class GetBansRequest : IRestRequest<UserReference[]> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetInviteRequest : IRestRequest<InviteReference> | |||||
| public class GetInviteRequest : IRestRequest<InviteReference> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"invite/{InviteCode}"; | string IRestRequest.Endpoint => $"invite/{InviteCode}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetInvitesRequest : IRestRequest<InviteReference[]> | |||||
| public class GetInvitesRequest : IRestRequest<InviteReference[]> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; | ||||
| @@ -4,7 +4,7 @@ using System.Text; | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetMessagesRequest : IRestRequest<Message[]> | |||||
| public class GetMessagesRequest : IRestRequest<Message[]> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint | string IRestRequest.Endpoint | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]> | |||||
| public class GetVoiceRegionsRequest : IRestRequest<GetVoiceRegionsResponse[]> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"voice/regions"; | string IRestRequest.Endpoint => $"voice/regions"; | ||||
| @@ -11,7 +11,7 @@ namespace Discord.API.Client.Rest | |||||
| bool IRestRequest.IsPrivate => false; | bool IRestRequest.IsPrivate => false; | ||||
| } | } | ||||
| public sealed class GetVoiceRegionsResponse | |||||
| public class GetVoiceRegionsResponse | |||||
| { | { | ||||
| [JsonProperty("sample_hostname")] | [JsonProperty("sample_hostname")] | ||||
| public string Hostname { get; set; } | public string Hostname { get; set; } | ||||
| @@ -4,7 +4,7 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class GetWidgetRequest : IRestRequest<GetWidgetResponse> | |||||
| public class GetWidgetRequest : IRestRequest<GetWidgetResponse> | |||||
| { | { | ||||
| string IRestRequest.Method => "GET"; | string IRestRequest.Method => "GET"; | ||||
| string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; | string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; | ||||
| @@ -19,9 +19,9 @@ namespace Discord.API.Client.Rest | |||||
| } | } | ||||
| } | } | ||||
| public sealed class GetWidgetResponse | |||||
| public class GetWidgetResponse | |||||
| { | { | ||||
| public sealed class Channel | |||||
| public class Channel | |||||
| { | { | ||||
| [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| @@ -30,7 +30,7 @@ namespace Discord.API.Client.Rest | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| public int Position { get; set; } | public int Position { get; set; } | ||||
| } | } | ||||
| public sealed class User : UserReference | |||||
| public class User : UserReference | |||||
| { | { | ||||
| [JsonProperty("avatar_url")] | [JsonProperty("avatar_url")] | ||||
| public string AvatarUrl { get; set; } | public string AvatarUrl { get; set; } | ||||
| @@ -39,7 +39,7 @@ namespace Discord.API.Client.Rest | |||||
| [JsonProperty("game")] | [JsonProperty("game")] | ||||
| public UserGame Game { get; set; } | public UserGame Game { get; set; } | ||||
| } | } | ||||
| public sealed class UserGame | |||||
| public class UserGame | |||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public int Id { get; set; } | public int Id { get; set; } | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class KickMemberRequest : IRestRequest | |||||
| public class KickMemberRequest : IRestRequest | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class LeaveGuildRequest : IRestRequest<Guild> | |||||
| public class LeaveGuildRequest : IRestRequest<Guild> | |||||
| { | { | ||||
| string IRestRequest.Method => "DELETE"; | string IRestRequest.Method => "DELETE"; | ||||
| string IRestRequest.Endpoint => $"guilds/{GuildId}"; | string IRestRequest.Endpoint => $"guilds/{GuildId}"; | ||||
| @@ -3,7 +3,7 @@ | |||||
| namespace Discord.API.Client.Rest | namespace Discord.API.Client.Rest | ||||
| { | { | ||||
| [JsonObject(MemberSerialization.OptIn)] | [JsonObject(MemberSerialization.OptIn)] | ||||
| public sealed class LoginRequest : IRestRequest<LoginResponse> | |||||
| public class LoginRequest : IRestRequest<LoginResponse> | |||||
| { | { | ||||
| string IRestRequest.Method => Email != null ? "POST" : "GET"; | string IRestRequest.Method => Email != null ? "POST" : "GET"; | ||||
| string IRestRequest.Endpoint => $"auth/login"; | string IRestRequest.Endpoint => $"auth/login"; | ||||
| @@ -16,7 +16,7 @@ namespace Discord.API.Client.Rest | |||||
| public string Password { get; set; } | public string Password { get; set; } | ||||
| } | } | ||||
| public sealed class LoginResponse | |||||
| public class LoginResponse | |||||
| { | { | ||||
| [JsonProperty("token")] | [JsonProperty("token")] | ||||
| public string Token { get; set; } | public string Token { get; set; } | ||||
| @@ -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"; | ||||
| @@ -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; } | ||||
| @@ -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}"; | ||||
| @@ -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}"; | ||||
| @@ -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; } | ||||
| @@ -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; } | ||||
| @@ -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"; | ||||
| @@ -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"; | ||||
| @@ -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"; | ||||
| @@ -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}"; | ||||
| @@ -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}"; | ||||
| @@ -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}"; | ||||
| @@ -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}"; | ||||
| @@ -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"; | ||||
| @@ -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}"; | ||||
| @@ -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(); | ||||
| @@ -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; | ||||
| @@ -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; } | ||||
| @@ -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; | ||||
| @@ -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; } | ||||
| @@ -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; } | ||||
| @@ -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; } | ||||
| @@ -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) | ||||