| @@ -1,17 +0,0 @@ | |||||
| using Discord.Net.WebSockets; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| public class AudioConfig | |||||
| { | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary> | |||||
| public int ConnectionTimeout { get; set; } = 30000; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | |||||
| public int ReconnectDelay { get; set; } = 1000; | |||||
| /// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | |||||
| public int FailedReconnectDelay { get; set; } = 15000; | |||||
| /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | |||||
| public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); | |||||
| } | |||||
| } | |||||
| @@ -1,18 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <Project ToolsVersion="14.0.25123" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <PropertyGroup> | |||||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25123</VisualStudioVersion> | |||||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||||
| <PropertyGroup Label="Globals"> | |||||
| <ProjectGuid>ddfcc44f-934e-478a-978c-69cdda2a1c5b</ProjectGuid> | |||||
| <RootNamespace>Discord.Audio</RootNamespace> | |||||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||||
| <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup> | |||||
| <SchemaVersion>2.0</SchemaVersion> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||||
| </Project> | |||||
| @@ -1,6 +0,0 @@ | |||||
| namespace Discord.Audio | |||||
| { | |||||
| internal class Logger | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -1,74 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public class AsyncEvent<T> | |||||
| { | |||||
| private readonly object _subLock = new object(); | |||||
| internal ImmutableArray<T> _subscriptions; | |||||
| public IReadOnlyList<T> Subscriptions => _subscriptions; | |||||
| public AsyncEvent() | |||||
| { | |||||
| _subscriptions = ImmutableArray.Create<T>(); | |||||
| } | |||||
| public void Add(T subscriber) | |||||
| { | |||||
| lock (_subLock) | |||||
| _subscriptions = _subscriptions.Add(subscriber); | |||||
| } | |||||
| public void Remove(T subscriber) | |||||
| { | |||||
| lock (_subLock) | |||||
| _subscriptions = _subscriptions.Remove(subscriber); | |||||
| } | |||||
| } | |||||
| public static class EventExtensions | |||||
| { | |||||
| public static async Task InvokeAsync(this AsyncEvent<Func<Task>> eventHandler) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| if (subscribers.Count > 0) | |||||
| { | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke().ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| public static async Task InvokeAsync<T>(this AsyncEvent<Func<T, Task>> eventHandler, T arg) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke(arg).ConfigureAwait(false); | |||||
| } | |||||
| public static async Task InvokeAsync<T1, T2>(this AsyncEvent<Func<T1, T2, Task>> eventHandler, T1 arg1, T2 arg2) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke(arg1, arg2).ConfigureAwait(false); | |||||
| } | |||||
| public static async Task InvokeAsync<T1, T2, T3>(this AsyncEvent<Func<T1, T2, T3, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke(arg1, arg2, arg3).ConfigureAwait(false); | |||||
| } | |||||
| public static async Task InvokeAsync<T1, T2, T3, T4>(this AsyncEvent<Func<T1, T2, T3, T4, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false); | |||||
| } | |||||
| public static async Task InvokeAsync<T1, T2, T3, T4, T5>(this AsyncEvent<Func<T1, T2, T3, T4, T5, Task>> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) | |||||
| { | |||||
| var subscribers = eventHandler.Subscriptions; | |||||
| for (int i = 0; i < subscribers.Count; i++) | |||||
| await subscribers[i].Invoke(arg1, arg2, arg3, arg4, arg5).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,35 +0,0 @@ | |||||
| { | |||||
| "version": "1.0.0-dev", | |||||
| "description": "A Discord.Net extension adding audio support.", | |||||
| "authors": [ "RogueException" ], | |||||
| "packOptions": { | |||||
| "tags": [ "discord", "discordapp" ], | |||||
| "licenseUrl": "http://opensource.org/licenses/MIT", | |||||
| "projectUrl": "https://github.com/RogueException/Discord.Net", | |||||
| "repository": { | |||||
| "type": "git", | |||||
| "url": "git://github.com/RogueException/Discord.Net" | |||||
| } | |||||
| }, | |||||
| "buildOptions": { | |||||
| "allowUnsafe": true, | |||||
| "warningsAsErrors": false | |||||
| }, | |||||
| "dependencies": { | |||||
| "Discord.Net": "1.0.0-dev", | |||||
| "System.Runtime.InteropServices": "4.1.0" | |||||
| }, | |||||
| "frameworks": { | |||||
| "netstandard1.3": { | |||||
| "imports": [ | |||||
| "dotnet5.4", | |||||
| "dnxcore50", | |||||
| "portable-net45+win8" | |||||
| ] | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -398,6 +398,17 @@ namespace Discord.API | |||||
| { | { | ||||
| await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false); | await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | |||||
| { | |||||
| var payload = new VoiceStateUpdateParams | |||||
| { | |||||
| GuildId = guildId, | |||||
| ChannelId = channelId, | |||||
| SelfDeaf = selfDeaf, | |||||
| SelfMute = selfMute | |||||
| }; | |||||
| await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Channels | //Channels | ||||
| public async Task<Channel> GetChannelAsync(ulong channelId, RequestOptions options = null) | public async Task<Channel> GetChannelAsync(ulong channelId, RequestOptions options = null) | ||||
| @@ -14,7 +14,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class AudioAPIClient | |||||
| public class DiscordVoiceAPIClient | |||||
| { | { | ||||
| public const int MaxBitrate = 128; | public const int MaxBitrate = 128; | ||||
| private const string Mode = "xsalsa20_poly1305"; | private const string Mode = "xsalsa20_poly1305"; | ||||
| @@ -40,7 +40,7 @@ namespace Discord.Audio | |||||
| public string SessionId { get; } | public string SessionId { get; } | ||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| internal AudioAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null) | |||||
| internal DiscordVoiceAPIClient(ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, JsonSerializer serializer = null) | |||||
| { | { | ||||
| GuildId = guildId; | GuildId = guildId; | ||||
| _userId = userId; | _userId = userId; | ||||
| @@ -0,0 +1,12 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| public class GuildEmojiUpdateEvent | |||||
| { | |||||
| [JsonProperty("guild_id")] | |||||
| public ulong GuildId; | |||||
| [JsonProperty("emojis")] | |||||
| public Emoji[] Emojis; | |||||
| } | |||||
| } | |||||
| @@ -1,15 +1,19 @@ | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | |||||
| namespace Discord.API.Gateway | namespace Discord.API.Gateway | ||||
| { | { | ||||
| public class RequestMembersParams | public class RequestMembersParams | ||||
| { | { | ||||
| [JsonProperty("guild_id")] | |||||
| public IEnumerable<ulong> GuildIds { get; set; } | |||||
| [JsonProperty("query")] | [JsonProperty("query")] | ||||
| public string Query { get; set; } | public string Query { get; set; } | ||||
| [JsonProperty("limit")] | [JsonProperty("limit")] | ||||
| public int Limit { get; set; } | public int Limit { get; set; } | ||||
| [JsonProperty("guild_id")] | |||||
| public IEnumerable<ulong> GuildIds { get; set; } | |||||
| [JsonIgnore] | |||||
| public IEnumerable<IGuild> Guilds { set { GuildIds = value.Select(x => x.Id); } } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,16 +0,0 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| public class UpdateVoiceParams | |||||
| { | |||||
| [JsonProperty("guild_id")] | |||||
| public ulong? GuildId { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public ulong? ChannelId { get; set; } | |||||
| [JsonProperty("self_mute")] | |||||
| public bool IsSelfMuted { get; set; } | |||||
| [JsonProperty("self_deaf")] | |||||
| public bool IsSelfDeafened { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| public class VoiceStateUpdateParams | |||||
| { | |||||
| [JsonProperty("self_mute")] | |||||
| public bool SelfMute { get; set; } | |||||
| [JsonProperty("self_deaf")] | |||||
| public bool SelfDeaf { get; set; } | |||||
| [JsonProperty("guild_id")] | |||||
| public ulong GuildId { get; set; } | |||||
| [JsonIgnore] | |||||
| public IGuild Guild { set { GuildId = value.Id; } } | |||||
| [JsonProperty("channel_id")] | |||||
| public ulong? ChannelId { get; set; } | |||||
| [JsonIgnore] | |||||
| public IChannel Channel { set { ChannelId = value?.Id; } } | |||||
| } | |||||
| } | |||||
| @@ -1,15 +1,15 @@ | |||||
| using Discord.API.Voice; | using Discord.API.Voice; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| using Discord.Net.WebSockets; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | |||||
| using System; | using System; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public class AudioClient | |||||
| internal class AudioClient : IAudioClient | |||||
| { | { | ||||
| public event Func<Task> Connected | public event Func<Task> Connected | ||||
| { | { | ||||
| @@ -30,12 +30,11 @@ namespace Discord.Audio | |||||
| } | } | ||||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | ||||
| private readonly ILogger _webSocketLogger; | |||||
| private readonly ILogger _webSocketLogger, _udpLogger; | |||||
| #if BENCHMARK | #if BENCHMARK | ||||
| private readonly ILogger _benchmarkLogger; | private readonly ILogger _benchmarkLogger; | ||||
| #endif | #endif | ||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | |||||
| internal readonly SemaphoreSlim _connectionLock; | internal readonly SemaphoreSlim _connectionLock; | ||||
| private TaskCompletionSource<bool> _connectTask; | private TaskCompletionSource<bool> _connectTask; | ||||
| @@ -45,20 +44,18 @@ namespace Discord.Audio | |||||
| private bool _isReconnecting; | private bool _isReconnecting; | ||||
| private string _url; | private string _url; | ||||
| public AudioAPIClient ApiClient { get; private set; } | |||||
| /// <summary> Gets the current connection state of this client. </summary> | |||||
| private DiscordSocketClient Discord { get; } | |||||
| public DiscordVoiceAPIClient ApiClient { get; private set; } | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||||
| public int Latency { get; private set; } | public int Latency { get; private set; } | ||||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | /// <summary> Creates a new REST/WebSocket discord client. </summary> | ||||
| internal AudioClient(ulong guildId, ulong userId, string sessionId, string token, AudioConfig config, ILogManager logManager) | |||||
| internal AudioClient(DiscordSocketClient discord, ulong guildId, ulong userId, string sessionId, string token, WebSocketProvider webSocketProvider, ILogManager logManager) | |||||
| { | { | ||||
| _connectionTimeout = config.ConnectionTimeout; | |||||
| _reconnectDelay = config.ReconnectDelay; | |||||
| _failedReconnectDelay = config.FailedReconnectDelay; | |||||
| Discord = discord; | |||||
| _webSocketLogger = logManager.CreateLogger("AudioWS"); | |||||
| _webSocketLogger = logManager.CreateLogger("Audio"); | |||||
| _udpLogger = logManager.CreateLogger("AudioUDP"); | |||||
| #if BENCHMARK | #if BENCHMARK | ||||
| _benchmarkLogger = logManager.CreateLogger("Benchmark"); | _benchmarkLogger = logManager.CreateLogger("Benchmark"); | ||||
| #endif | #endif | ||||
| @@ -72,8 +69,7 @@ namespace Discord.Audio | |||||
| e.ErrorContext.Handled = true; | e.ErrorContext.Handled = true; | ||||
| }; | }; | ||||
| var webSocketProvider = config.WebSocketProvider; //TODO: Clean this check | |||||
| ApiClient = new AudioAPIClient(guildId, userId, sessionId, token, config.WebSocketProvider); | |||||
| ApiClient = new DiscordVoiceAPIClient(guildId, userId, sessionId, token, webSocketProvider); | |||||
| ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false); | ApiClient.SentGatewayMessage += async opCode => await _webSocketLogger.DebugAsync($"Sent {(VoiceOpCode)opCode}").ConfigureAwait(false); | ||||
| ApiClient.ReceivedEvent += ProcessMessageAsync; | ApiClient.ReceivedEvent += ProcessMessageAsync; | ||||
| @@ -1,7 +1,11 @@ | |||||
| namespace Discord.Audio | |||||
| using System; | |||||
| namespace Discord.Audio | |||||
| { | { | ||||
| [Flags] | |||||
| public enum AudioMode : byte | public enum AudioMode : byte | ||||
| { | { | ||||
| Disabled = 0, | |||||
| Outgoing = 1, | Outgoing = 1, | ||||
| Incoming = 2, | Incoming = 2, | ||||
| Both = Outgoing | Incoming | Both = Outgoing | Incoming | ||||
| @@ -0,0 +1,20 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| public interface IAudioClient | |||||
| { | |||||
| event Func<Task> Connected; | |||||
| event Func<Task> Disconnected; | |||||
| event Func<int, int, Task> LatencyUpdated; | |||||
| DiscordVoiceAPIClient ApiClient { get; } | |||||
| /// <summary> Gets the current connection state of this client. </summary> | |||||
| ConnectionState ConnectionState { get; } | |||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||||
| int Latency { get; } | |||||
| Task DisconnectAsync(); | |||||
| } | |||||
| } | |||||
| @@ -36,7 +36,7 @@ namespace Discord.Audio.Opus | |||||
| { | { | ||||
| if (channels != 1 && channels != 2) | if (channels != 1 && channels != 2) | ||||
| throw new ArgumentOutOfRangeException(nameof(channels)); | throw new ArgumentOutOfRangeException(nameof(channels)); | ||||
| if (bitrate != null && (bitrate < 1 || bitrate > AudioAPIClient.MaxBitrate)) | |||||
| if (bitrate != null && (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate)) | |||||
| throw new ArgumentOutOfRangeException(nameof(bitrate)); | throw new ArgumentOutOfRangeException(nameof(bitrate)); | ||||
| OpusError error; | OpusError error; | ||||
| @@ -1,8 +1,8 @@ | |||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
| namespace Discord.Net.Audio | |||||
| namespace Discord.Net.Audio.Sodium | |||||
| { | { | ||||
| public unsafe static class LibSodium | |||||
| public unsafe static class SecretBox | |||||
| { | { | ||||
| [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
| private static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret); | private static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret); | ||||
| @@ -3,6 +3,7 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: Add event docstrings | |||||
| public partial class DiscordSocketClient | public partial class DiscordSocketClient | ||||
| { | { | ||||
| //General | //General | ||||
| @@ -1,7 +1,9 @@ | |||||
| using Discord.API.Gateway; | using Discord.API.Gateway; | ||||
| using Discord.Audio; | |||||
| using Discord.Extensions; | using Discord.Extensions; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| using Discord.Net.WebSockets; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| @@ -14,9 +16,6 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| //TODO: Add event docstrings | |||||
| //TODO: Add reconnect logic (+ensure the heartbeat task to shut down) | |||||
| //TODO: Add resume logic | |||||
| public partial class DiscordSocketClient : DiscordClient, IDiscordClient | public partial class DiscordSocketClient : DiscordClient, IDiscordClient | ||||
| { | { | ||||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | private readonly ConcurrentQueue<ulong> _largeGuilds; | ||||
| @@ -25,9 +24,6 @@ namespace Discord | |||||
| private readonly ILogger _benchmarkLogger; | private readonly ILogger _benchmarkLogger; | ||||
| #endif | #endif | ||||
| private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
| private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; | |||||
| private readonly int _largeThreshold; | |||||
| private readonly int _totalShards; | |||||
| private string _sessionId; | private string _sessionId; | ||||
| private int _lastSeq; | private int _lastSeq; | ||||
| @@ -46,9 +42,17 @@ namespace Discord | |||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
| public int Latency { get; private set; } | public int Latency { get; private set; } | ||||
| //From DiscordConfig | |||||
| internal int TotalShards { get; private set; } | |||||
| internal int ConnectionTimeout { get; private set; } | |||||
| internal int ReconnectDelay { get; private set; } | |||||
| internal int FailedReconnectDelay { get; private set; } | |||||
| internal int MessageCacheSize { get; private set; } | internal int MessageCacheSize { get; private set; } | ||||
| internal int LargeThreshold { get; private set; } | |||||
| internal AudioMode AudioMode { get; private set; } | |||||
| internal DataStore DataStore { get; private set; } | internal DataStore DataStore { get; private set; } | ||||
| internal WebSocketProvider WebSocketProvider { get; private set; } | |||||
| internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser; | internal CachedSelfUser CurrentUser => _currentUser as CachedSelfUser; | ||||
| internal IReadOnlyCollection<CachedGuild> Guilds => DataStore.Guilds; | internal IReadOnlyCollection<CachedGuild> Guilds => DataStore.Guilds; | ||||
| @@ -62,15 +66,15 @@ namespace Discord | |||||
| : base(config) | : base(config) | ||||
| { | { | ||||
| ShardId = config.ShardId; | ShardId = config.ShardId; | ||||
| _totalShards = config.TotalShards; | |||||
| _connectionTimeout = config.ConnectionTimeout; | |||||
| _reconnectDelay = config.ReconnectDelay; | |||||
| _failedReconnectDelay = config.FailedReconnectDelay; | |||||
| TotalShards = config.TotalShards; | |||||
| ConnectionTimeout = config.ConnectionTimeout; | |||||
| ReconnectDelay = config.ReconnectDelay; | |||||
| FailedReconnectDelay = config.FailedReconnectDelay; | |||||
| MessageCacheSize = config.MessageCacheSize; | MessageCacheSize = config.MessageCacheSize; | ||||
| _largeThreshold = config.LargeThreshold; | |||||
| LargeThreshold = config.LargeThreshold; | |||||
| AudioMode = config.AudioMode; | |||||
| WebSocketProvider = config.WebSocketProvider; | |||||
| _gatewayLogger = _log.CreateLogger("Gateway"); | _gatewayLogger = _log.CreateLogger("Gateway"); | ||||
| #if BENCHMARK | #if BENCHMARK | ||||
| _benchmarkLogger = _log.CreateLogger("Benchmark"); | _benchmarkLogger = _log.CreateLogger("Benchmark"); | ||||
| @@ -471,7 +475,7 @@ namespace Discord | |||||
| case GatewayOpCode.Dispatch: | case GatewayOpCode.Dispatch: | ||||
| switch (type) | switch (type) | ||||
| { | { | ||||
| //Global | |||||
| //Connection | |||||
| case "READY": | case "READY": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | ||||
| @@ -507,6 +511,11 @@ namespace Discord | |||||
| await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | ||||
| } | } | ||||
| break; | break; | ||||
| case "RESUMED": | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); | |||||
| await _gatewayLogger.InfoAsync("Resume").ConfigureAwait(false); | |||||
| return; | |||||
| //Guilds | //Guilds | ||||
| case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
| @@ -569,6 +578,28 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "GUILD_EMOJI_UPDATE": //TODO: Add | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJI_UPDATE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<API.Gateway.GuildEmojiUpdateEvent>(_serializer); | |||||
| var guild = DataStore.GetGuild(data.GuildId); | |||||
| if (guild != null) | |||||
| { | |||||
| var before = guild.Clone(); | |||||
| guild.Update(data, UpdateSource.WebSocket); | |||||
| await _guildUpdatedEvent.InvokeAsync(before, guild).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("GUILD_EMOJI_UPDATE referenced an unknown guild.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| return; | |||||
| case "GUILD_INTEGRATIONS_UPDATE": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "GUILD_DELETE": | case "GUILD_DELETE": | ||||
| { | { | ||||
| var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer); | ||||
| @@ -1099,26 +1130,17 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "VOICE_SERVER_UPDATE": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| //Ignored | |||||
| //Ignored (User only) | |||||
| case "USER_SETTINGS_UPDATE": | case "USER_SETTINGS_UPDATE": | ||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); | ||||
| return; | return; | ||||
| case "MESSAGE_ACK": //TODO: Add (User only) | |||||
| case "MESSAGE_ACK": | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); | ||||
| return; | return; | ||||
| case "GUILD_EMOJIS_UPDATE": //TODO: Add | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "VOICE_SERVER_UPDATE": //TODO: Add | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); | |||||
| return; | |||||
| case "RESUMED": //TODO: Add | |||||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (RESUMED)").ConfigureAwait(false); | |||||
| return; | |||||
| //Others | //Others | ||||
| default: | default: | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Net.WebSockets; | |||||
| using Discord.Audio; | |||||
| using Discord.Net.WebSockets; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -28,7 +29,10 @@ namespace Discord | |||||
| /// Decreasing this may reduce CPU usage while increasing login time and network usage. | /// Decreasing this may reduce CPU usage while increasing login time and network usage. | ||||
| /// </summary> | /// </summary> | ||||
| public int LargeThreshold { get; set; } = 250; | public int LargeThreshold { get; set; } = 250; | ||||
| /// <summary> Gets or sets the type of audio this DiscordClient supports. </summary> | |||||
| public AudioMode AudioMode { get; set; } = AudioMode.Disabled; | |||||
| /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | ||||
| public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); | public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Audio; | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -13,5 +14,7 @@ namespace Discord | |||||
| /// <summary> Modifies this voice channel. </summary> | /// <summary> Modifies this voice channel. </summary> | ||||
| Task ModifyAsync(Action<ModifyVoiceChannelParams> func); | Task ModifyAsync(Action<ModifyVoiceChannelParams> func); | ||||
| /// <summary> Connects to this voice channel. </summary> | |||||
| Task<IAudioClient> ConnectAsync(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ using System.Collections.Generic; | |||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using Discord.Audio; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -49,6 +50,8 @@ namespace Discord | |||||
| throw new NotSupportedException(); | throw new NotSupportedException(); | ||||
| } | } | ||||
| public virtual Task<IAudioClient> ConnectAsync() { throw new NotSupportedException(); } | |||||
| private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; | private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Audio; | |||||
| using Discord.Extensions; | using Discord.Extensions; | ||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| @@ -311,6 +312,7 @@ namespace Discord | |||||
| IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis; | IReadOnlyCollection<Emoji> IGuild.Emojis => Emojis; | ||||
| IReadOnlyCollection<string> IGuild.Features => Features; | IReadOnlyCollection<string> IGuild.Features => Features; | ||||
| Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } | ||||
| IAudioClient IGuild.AudioClient => null; | |||||
| IRole IGuild.GetRole(ulong id) => GetRole(id); | IRole IGuild.GetRole(ulong id) => GetRole(id); | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Audio; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -37,6 +38,8 @@ namespace Discord | |||||
| /// <summary> Gets the id of the region hosting this guild's voice channels. </summary> | /// <summary> Gets the id of the region hosting this guild's voice channels. </summary> | ||||
| string VoiceRegionId { get; } | string VoiceRegionId { get; } | ||||
| /// <summary> Gets the IAudioClient currently associated with this guild. </summary> | |||||
| IAudioClient AudioClient { get; } | |||||
| /// <summary> Gets the built-in role containing all users in this guild. </summary> | /// <summary> Gets the built-in role containing all users in this guild. </summary> | ||||
| IRole EveryoneRole { get; } | IRole EveryoneRole { get; } | ||||
| /// <summary> Gets a collection of all custom emojis for this guild. </summary> | /// <summary> Gets a collection of all custom emojis for this guild. </summary> | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Extensions; | |||||
| using Discord.Audio; | |||||
| using Discord.Extensions; | |||||
| using System; | using System; | ||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -6,6 +7,7 @@ using System.Collections.Immutable; | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using ChannelModel = Discord.API.Channel; | using ChannelModel = Discord.API.Channel; | ||||
| using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent; | |||||
| using ExtendedModel = Discord.API.Gateway.ExtendedGuild; | using ExtendedModel = Discord.API.Gateway.ExtendedGuild; | ||||
| using MemberModel = Discord.API.GuildMember; | using MemberModel = Discord.API.GuildMember; | ||||
| using Model = Discord.API.Guild; | using Model = Discord.API.Guild; | ||||
| @@ -25,6 +27,7 @@ namespace Discord | |||||
| public bool Available { get; private set; } | public bool Available { get; private set; } | ||||
| public int MemberCount { get; private set; } | public int MemberCount { get; private set; } | ||||
| public int DownloadedMemberCount { get; private set; } | public int DownloadedMemberCount { get; private set; } | ||||
| public IAudioClient AudioClient { get; private set; } | |||||
| public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | public bool HasAllMembers => _downloaderPromise.Task.IsCompleted; | ||||
| public Task DownloaderPromise => _downloaderPromise.Task; | public Task DownloaderPromise => _downloaderPromise.Task; | ||||
| @@ -102,6 +105,16 @@ namespace Discord | |||||
| _voiceStates = voiceStates; | _voiceStates = voiceStates; | ||||
| } | } | ||||
| public void Update(EmojiUpdateModel model, UpdateSource source) | |||||
| { | |||||
| if (source == UpdateSource.Rest && IsAttached) return; | |||||
| var emojis = ImmutableArray.CreateBuilder<Emoji>(model.Emojis.Length); | |||||
| for (int i = 0; i < model.Emojis.Length; i++) | |||||
| emojis.Add(new Emoji(model.Emojis[i])); | |||||
| Emojis = emojis.ToImmutableArray(); | |||||
| } | |||||
| public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id)); | public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id)); | ||||
| public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | ||||
| public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | ||||
| @@ -1,4 +1,6 @@ | |||||
| using System.Collections.Generic; | |||||
| using Discord.Audio; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -33,6 +35,19 @@ namespace Discord | |||||
| return null; | return null; | ||||
| } | } | ||||
| public override async Task<IAudioClient> ConnectAsync() | |||||
| { | |||||
| var audioMode = Discord.AudioMode; | |||||
| if (audioMode == AudioMode.Disabled) | |||||
| throw new InvalidOperationException($"Audio is not enabled on this client, {nameof(DiscordSocketConfig.AudioMode)} in {nameof(DiscordSocketConfig)} must be set."); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, Id, | |||||
| (audioMode & AudioMode.Incoming) == 0, | |||||
| (audioMode & AudioMode.Outgoing) == 0).ConfigureAwait(false); | |||||
| return null; | |||||
| //TODO: Block and return | |||||
| } | |||||
| public CachedVoiceChannel Clone() => MemberwiseClone() as CachedVoiceChannel; | public CachedVoiceChannel Clone() => MemberwiseClone() as CachedVoiceChannel; | ||||
| ICachedChannel ICachedChannel.Clone() => Clone(); | ICachedChannel ICachedChannel.Clone() => Clone(); | ||||
| @@ -28,6 +28,7 @@ | |||||
| "System.Net.Http": "4.1.0", | "System.Net.Http": "4.1.0", | ||||
| "System.Net.WebSockets.Client": "4.0.0", | "System.Net.WebSockets.Client": "4.0.0", | ||||
| "System.Reflection.Extensions": "4.0.1", | "System.Reflection.Extensions": "4.0.1", | ||||
| "System.Runtime.InteropServices": "4.1.0", | |||||
| "System.Runtime.Serialization.Primitives": "4.1.1", | "System.Runtime.Serialization.Primitives": "4.1.1", | ||||
| "System.Text.RegularExpressions": "4.1.0" | "System.Text.RegularExpressions": "4.1.0" | ||||
| }, | }, | ||||