From c01e3c069df4d5e7d7cee75da0a398649348a8db Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 20:23:57 -0300 Subject: [PATCH 01/39] Prep for WebSocket client, Several MessageQueue fixes --- src/Discord.Net/API/DiscordRawClient.cs | 67 ++-- src/Discord.Net/API/IWebSocketMessage.cs | 9 + src/Discord.Net/API/WebSocketMessage.cs | 23 ++ .../Entities/Guilds/IGuildIntegration.cs | 2 +- .../Entities/Guilds/IIntegrationAccount.cs | 7 - .../Entities/Guilds/IntegrationAccount.cs | 4 +- .../Entities/Guilds/VoiceRegion.cs | 0 .../Entities/Invites/Invite.cs | 4 +- .../Entities/Invites/PublicInvite.cs | 4 +- .../Permissions/ChannelPermissions.cs | 2 +- .../Entities/Users/Connection.cs | 11 +- .../Common/Entities/Users/IConnection.cs | 2 +- src/Discord.Net/Discord.Net.csproj | 30 +- src/Discord.Net/DiscordConfig.cs | 4 +- .../Net/Converters/UInt64ArrayConverter.cs | 2 +- src/Discord.Net/Net/Rest/DefaultRestClient.cs | 12 +- src/Discord.Net/Net/Rest/IRestClient.cs | 5 +- .../Net/Rest/RequestQueue/RequestQueue.cs | 41 +- .../Rest/RequestQueue/RequestQueueBucket.cs | 57 +-- .../Net/Rest/RequestQueue/RestRequest.cs | 4 +- .../Net/Rest/RestClientProvider.cs | 2 +- src/Discord.Net/Rest/DiscordClient.cs | 44 ++- .../Rest/Entities/Guilds/GuildEmbed.cs | 7 +- .../Rest/Entities/Guilds/GuildIntegration.cs | 2 +- .../Rest/Entities/Guilds/UserGuild.cs | 10 +- .../Rest/Entities/Invites/GuildInvite.cs | 2 +- src/Discord.Net/Rest/Entities/Message.cs | 1 - src/Discord.Net/Rest/Entities/Users/User.cs | 5 +- .../Caches/ChannelPermissionsCache.cs | 71 ++++ .../WebSocket/Caches/MessageCache.cs | 98 +++++ src/Discord.Net/WebSocket/DiscordClient.cs | 139 +++++++ .../WebSocket/Entities/Channels/DMChannel.cs | 141 +++++++ .../Entities/Channels/GuildChannel.cs | 171 ++++++++ .../Entities/Channels/TextChannel.cs | 122 ++++++ .../Entities/Channels/VoiceChannel.cs | 45 +++ .../WebSocket/Entities/Guilds/Guild.cs | 374 ++++++++++++++++++ .../Entities/Guilds/GuildIntegration.cs | 87 ++++ .../WebSocket/Entities/Invites/GuildInvite.cs | 52 +++ src/Discord.Net/WebSocket/Entities/Message.cs | 146 +++++++ src/Discord.Net/WebSocket/Entities/Role.cs | 80 ++++ .../WebSocket/Entities/Users/DMUser.cs | 20 + .../WebSocket/Entities/Users/GuildUser.cs | 117 ++++++ .../WebSocket/Entities/Users/PublicUser.cs | 15 + .../WebSocket/Entities/Users/SelfUser.cs | 48 +++ .../WebSocket/Entities/Users/User.cs | 65 +++ 45 files changed, 1994 insertions(+), 160 deletions(-) create mode 100644 src/Discord.Net/API/IWebSocketMessage.cs create mode 100644 src/Discord.Net/API/WebSocketMessage.cs delete mode 100644 src/Discord.Net/Common/Entities/Guilds/IIntegrationAccount.cs rename src/Discord.Net/{Rest => Common}/Entities/Guilds/IntegrationAccount.cs (72%) rename src/Discord.Net/{Rest => Common}/Entities/Guilds/VoiceRegion.cs (100%) rename src/Discord.Net/{Rest => Common}/Entities/Invites/Invite.cs (93%) rename src/Discord.Net/{Rest => Common}/Entities/Invites/PublicInvite.cs (88%) rename src/Discord.Net/{Rest => Common}/Entities/Users/Connection.cs (63%) create mode 100644 src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs create mode 100644 src/Discord.Net/WebSocket/Caches/MessageCache.cs create mode 100644 src/Discord.Net/WebSocket/DiscordClient.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Message.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Role.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Users/DMUser.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Users/User.cs diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 60b571f74..218964937 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -20,23 +20,22 @@ namespace Discord.API public class DiscordRawClient { internal event EventHandler SentRequest; - + private readonly RequestQueue _requestQueue; - private readonly IRestClient _restClient; - private readonly CancellationToken _cancelToken; private readonly JsonSerializer _serializer; - + private IRestClient _restClient; + private CancellationToken _cancelToken; + public TokenType AuthTokenType { get; private set; } public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - internal DiscordRawClient(RestClientProvider restClientProvider, CancellationToken cancelToken) + internal DiscordRawClient(RestClientProvider restClientProvider) { - _cancelToken = cancelToken; - - _restClient = restClientProvider(DiscordConfig.ClientAPIUrl, cancelToken); + _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); _restClient.SetHeader("accept", "*/*"); _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); + _requestQueue = new RequestQueue(_restClient); _serializer = new JsonSerializer(); @@ -53,29 +52,40 @@ namespace Discord.API _serializer.ContractResolver = new OptionalContractResolver(); } - public void SetToken(TokenType tokenType, string token) + public async Task Login(TokenType tokenType, string token, CancellationToken cancelToken) { AuthTokenType = tokenType; - - if (token != null) + _cancelToken = cancelToken; + await _requestQueue.SetCancelToken(cancelToken).ConfigureAwait(false); + + switch (tokenType) { - switch (tokenType) - { - case TokenType.Bot: - token = $"Bot {token}"; - break; - case TokenType.Bearer: - token = $"Bearer {token}"; - break; - case TokenType.User: - break; - default: - throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); - } + case TokenType.Bot: + token = $"Bot {token}"; + break; + case TokenType.Bearer: + token = $"Bearer {token}"; + break; + case TokenType.User: + break; + default: + throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); } _restClient.SetHeader("authorization", token); } + public async Task Login(LoginParams args, CancellationToken cancelToken) + { + var response = await Send("POST", "auth/login", args).ConfigureAwait(false); + + AuthTokenType = TokenType.User; + _restClient.SetHeader("authorization", response.Token); + } + public async Task Logout() + { + await _requestQueue.Clear().ConfigureAwait(false); + _restClient = null; + } //Core public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) @@ -121,6 +131,8 @@ namespace Discord.API private async Task SendInternal(string method, string endpoint, object payload, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { + _cancelToken.ThrowIfCancellationRequested(); + var stopwatch = Stopwatch.StartNew(); string json = null; if (payload != null) @@ -136,6 +148,8 @@ namespace Discord.API } private async Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { + _cancelToken.ThrowIfCancellationRequested(); + var stopwatch = Stopwatch.StartNew(); var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, multipartArgs, headerOnly), group, bucketId, guildId).ConfigureAwait(false); int bytes = headerOnly ? 0 : (int)responseStream.Length; @@ -149,11 +163,6 @@ namespace Discord.API //Auth - public async Task Login(LoginParams args) - { - var response = await Send("POST", "auth/login", args).ConfigureAwait(false); - SetToken(TokenType.User, response.Token); - } public async Task ValidateToken() { await Send("GET", "auth/login").ConfigureAwait(false); diff --git a/src/Discord.Net/API/IWebSocketMessage.cs b/src/Discord.Net/API/IWebSocketMessage.cs new file mode 100644 index 000000000..526f3119f --- /dev/null +++ b/src/Discord.Net/API/IWebSocketMessage.cs @@ -0,0 +1,9 @@ +namespace Discord.API +{ + public interface IWebSocketMessage + { + int OpCode { get; } + object Payload { get; } + bool IsPrivate { get; } + } +} diff --git a/src/Discord.Net/API/WebSocketMessage.cs b/src/Discord.Net/API/WebSocketMessage.cs new file mode 100644 index 000000000..05d5e779d --- /dev/null +++ b/src/Discord.Net/API/WebSocketMessage.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class WebSocketMessage + { + [JsonProperty("op")] + public int? Operation { get; set; } + [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] + public string Type { get; set; } + [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] + public uint? Sequence { get; set; } + [JsonProperty("d")] + public object Payload { get; set; } + + public WebSocketMessage() { } + public WebSocketMessage(IWebSocketMessage msg) + { + Operation = msg.OpCode; + Payload = msg.Payload; + } + } +} diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs b/src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs index 0252382fd..e90d8ae76 100644 --- a/src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs +++ b/src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs @@ -12,10 +12,10 @@ namespace Discord ulong ExpireBehavior { get; } ulong ExpireGracePeriod { get; } DateTime SyncedAt { get; } + IntegrationAccount Account { get; } IGuild Guild { get; } IUser User { get; } IRole Role { get; } - IIntegrationAccount Account { get; } } } diff --git a/src/Discord.Net/Common/Entities/Guilds/IIntegrationAccount.cs b/src/Discord.Net/Common/Entities/Guilds/IIntegrationAccount.cs deleted file mode 100644 index 7e5052c1b..000000000 --- a/src/Discord.Net/Common/Entities/Guilds/IIntegrationAccount.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Discord -{ - public interface IIntegrationAccount : IEntity - { - string Name { get; } - } -} diff --git a/src/Discord.Net/Rest/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs similarity index 72% rename from src/Discord.Net/Rest/Entities/Guilds/IntegrationAccount.cs rename to src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs index f28061955..7b49d9de7 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/IntegrationAccount.cs +++ b/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs @@ -1,6 +1,6 @@ -namespace Discord.Rest +namespace Discord { - public class IntegrationAccount : IIntegrationAccount + public struct IntegrationAccount { /// public string Id { get; } diff --git a/src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs b/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs similarity index 100% rename from src/Discord.Net/Rest/Entities/Guilds/VoiceRegion.cs rename to src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs diff --git a/src/Discord.Net/Rest/Entities/Invites/Invite.cs b/src/Discord.Net/Common/Entities/Invites/Invite.cs similarity index 93% rename from src/Discord.Net/Rest/Entities/Invites/Invite.cs rename to src/Discord.Net/Common/Entities/Invites/Invite.cs index 2e13b0542..9ea98fd18 100644 --- a/src/Discord.Net/Rest/Entities/Invites/Invite.cs +++ b/src/Discord.Net/Common/Entities/Invites/Invite.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Model = Discord.API.Invite; -namespace Discord.Rest +namespace Discord { public abstract class Invite : IInvite { @@ -17,7 +17,7 @@ namespace Discord.Rest /// public string XkcdUrl => XkcdCode != null ? $"{DiscordConfig.InviteUrl}/{XkcdCode}" : null; - internal abstract DiscordClient Discord { get; } + internal abstract IDiscordClient Discord { get; } internal Invite(Model model) { diff --git a/src/Discord.Net/Rest/Entities/Invites/PublicInvite.cs b/src/Discord.Net/Common/Entities/Invites/PublicInvite.cs similarity index 88% rename from src/Discord.Net/Rest/Entities/Invites/PublicInvite.cs rename to src/Discord.Net/Common/Entities/Invites/PublicInvite.cs index 8a767dc20..3a2f42394 100644 --- a/src/Discord.Net/Rest/Entities/Invites/PublicInvite.cs +++ b/src/Discord.Net/Common/Entities/Invites/PublicInvite.cs @@ -15,9 +15,9 @@ namespace Discord.Rest /// public ulong ChannelId => _channelId; - internal override DiscordClient Discord { get; } + internal override IDiscordClient Discord { get; } - internal PublicInvite(DiscordClient discord, Model model) + internal PublicInvite(IDiscordClient discord, Model model) : base(model) { Discord = discord; diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs index ffcc403cf..4c0710f82 100644 --- a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs @@ -18,7 +18,7 @@ namespace Discord { case ITextChannel _: return _allText; case IVoiceChannel _: return _allVoice; - case IDMChannel _: return _allDM; + case IGuildChannel _: return _allDM; default: throw new ArgumentException("Unknown channel type", nameof(channel)); } diff --git a/src/Discord.Net/Rest/Entities/Users/Connection.cs b/src/Discord.Net/Common/Entities/Users/Connection.cs similarity index 63% rename from src/Discord.Net/Rest/Entities/Users/Connection.cs rename to src/Discord.Net/Common/Entities/Users/Connection.cs index 9795dc207..fc4524e90 100644 --- a/src/Discord.Net/Rest/Entities/Users/Connection.cs +++ b/src/Discord.Net/Common/Entities/Users/Connection.cs @@ -6,12 +6,11 @@ namespace Discord.Rest public class Connection : IConnection { public string Id { get; } + public string Type { get; } + public string Name { get; } + public bool IsRevoked { get; } - public string Type { get; private set; } - public string Name { get; private set; } - public bool IsRevoked { get; private set; } - - public IEnumerable Integrations { get; private set; } + public IEnumerable IntegrationIds { get; } public Connection(Model model) { @@ -21,7 +20,7 @@ namespace Discord.Rest Name = model.Name; IsRevoked = model.Revoked; - Integrations = model.Integrations; + IntegrationIds = model.Integrations; } public override string ToString() => $"{Name ?? Id.ToString()} ({Type})"; diff --git a/src/Discord.Net/Common/Entities/Users/IConnection.cs b/src/Discord.Net/Common/Entities/Users/IConnection.cs index 3c9b5a79e..6540c147e 100644 --- a/src/Discord.Net/Common/Entities/Users/IConnection.cs +++ b/src/Discord.Net/Common/Entities/Users/IConnection.cs @@ -9,6 +9,6 @@ namespace Discord string Name { get; } bool IsRevoked { get; } - IEnumerable Integrations { get; } + IEnumerable IntegrationIds { get; } } } diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 48f2a4928..88794a5aa 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -67,6 +67,7 @@ + @@ -98,6 +99,7 @@ + @@ -111,7 +113,6 @@ - @@ -161,19 +162,19 @@ - - + + - - + + - + @@ -204,6 +205,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs index eef0bc638..50847dbb3 100644 --- a/src/Discord.Net/DiscordConfig.cs +++ b/src/Discord.Net/DiscordConfig.cs @@ -3,6 +3,8 @@ using System.Reflection; namespace Discord { + //TODO: Add socket config items in their own class + public class DiscordConfig { public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly?.GetName().Version.ToString(3) ?? "Unknown"; @@ -26,6 +28,6 @@ namespace Discord public LogSeverity LogLevel { get; set; } = LogSeverity.Info; /// Gets or sets the provider used to generate new REST connections. - public RestClientProvider RestClientProvider { get; set; } = (url, ct) => new DefaultRestClient(url, ct); + public RestClientProvider RestClientProvider { get; set; } = url => new DefaultRestClient(url); } } diff --git a/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs b/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs index f57e3427b..8e94b51f5 100644 --- a/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs +++ b/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs @@ -5,7 +5,7 @@ using System.Globalization; namespace Discord.Net.Converters { - internal class UInt64ArrayConverter : JsonConverter + public class UInt64ArrayConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); public override bool CanRead => true; diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net/Net/Rest/DefaultRestClient.cs index a2b859197..86dee1150 100644 --- a/src/Discord.Net/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net/Net/Rest/DefaultRestClient.cs @@ -17,13 +17,11 @@ namespace Discord.Net.Rest protected readonly HttpClient _client; protected readonly string _baseUrl; - protected readonly CancellationToken _cancelToken; protected bool _isDisposed; - public DefaultRestClient(string baseUrl, CancellationToken cancelToken) + public DefaultRestClient(string baseUrl) { _baseUrl = baseUrl; - _cancelToken = cancelToken; _client = new HttpClient(new HttpClientHandler { @@ -56,18 +54,18 @@ namespace Discord.Net.Rest _client.DefaultRequestHeaders.Add(key, value); } - public async Task Send(string method, string endpoint, string json = null, bool headerOnly = false) + public async Task Send(string method, string endpoint, CancellationToken cancelToken, string json = null, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (json != null) restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return await SendInternal(restRequest, _cancelToken, headerOnly).ConfigureAwait(false); + return await SendInternal(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false) + public async Task Send(string method, string endpoint, CancellationToken cancelToken, IReadOnlyDictionary multipartParams, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -97,7 +95,7 @@ namespace Discord.Net.Rest } } restRequest.Content = content; - return await SendInternal(restRequest, _cancelToken, headerOnly).ConfigureAwait(false); + return await SendInternal(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } diff --git a/src/Discord.Net/Net/Rest/IRestClient.cs b/src/Discord.Net/Net/Rest/IRestClient.cs index 3f99a2f7e..93740fc95 100644 --- a/src/Discord.Net/Net/Rest/IRestClient.cs +++ b/src/Discord.Net/Net/Rest/IRestClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace Discord.Net.Rest @@ -9,7 +10,7 @@ namespace Discord.Net.Rest { void SetHeader(string key, string value); - Task Send(string method, string endpoint, string json = null, bool headerOnly = false); - Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false); + Task Send(string method, string endpoint, CancellationToken cancelToken, string json = null, bool headerOnly = false); + Task Send(string method, string endpoint, CancellationToken cancelToken, IReadOnlyDictionary multipartParams, bool headerOnly = false); } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs index 155b683e7..39a657cb4 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs @@ -8,9 +8,12 @@ namespace Discord.Net.Rest { public class RequestQueue : IRequestQueue { - private SemaphoreSlim _lock; - private RequestQueueBucket[] _globalBuckets; - private Dictionary[] _guildBuckets; + private readonly SemaphoreSlim _lock; + private readonly RequestQueueBucket[] _globalBuckets; + private readonly Dictionary[] _guildBuckets; + private CancellationTokenSource _clearToken; + private CancellationToken? _parentToken; + private CancellationToken _cancelToken; public IRestClient RestClient { get; } @@ -21,12 +24,26 @@ namespace Discord.Net.Rest _lock = new SemaphoreSlim(1, 1); _globalBuckets = new RequestQueueBucket[Enum.GetValues(typeof(GlobalBucket)).Length]; _guildBuckets = new Dictionary[Enum.GetValues(typeof(GuildBucket)).Length]; + _clearToken = new CancellationTokenSource(); + _cancelToken = _clearToken.Token; + } + internal async Task SetCancelToken(CancellationToken cancelToken) + { + await Lock().ConfigureAwait(false); + try + { + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token).Token; + } + finally { Unlock(); } } internal async Task Send(RestRequest request, BucketGroup group, int bucketId, ulong guildId) { RequestQueueBucket bucket; + request.CancelToken = _cancelToken; + await Lock().ConfigureAwait(false); try { @@ -129,6 +146,20 @@ namespace Discord.Net.Rest _lock.Release(); } + public async Task Clear() + { + await Lock().ConfigureAwait(false); + try + { + _clearToken?.Cancel(); + _clearToken = new CancellationTokenSource(); + if (_parentToken != null) + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken.Value).Token; + else + _cancelToken = _clearToken.Token; + } + finally { Unlock(); } + } public async Task Clear(GlobalBucket type) { var bucket = _globalBuckets[(int)type]; @@ -136,7 +167,7 @@ namespace Discord.Net.Rest { try { - await bucket.Lock(); + await bucket.Lock().ConfigureAwait(false); bucket.Clear(); } finally { bucket.Unlock(); } @@ -152,7 +183,7 @@ namespace Discord.Net.Rest { try { - await bucket.Lock(); + await bucket.Lock().ConfigureAwait(false); bucket.Clear(); } finally { bucket.Unlock(); } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs index 2d14bc367..708e3251c 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs @@ -16,7 +16,7 @@ namespace Discord.Net.Rest private readonly ConcurrentQueue _queue; private readonly SemaphoreSlim _lock; private Task _resetTask; - private bool _waitingToProcess, _destroyed; //TODO: Remove _destroyed + private bool _waitingToProcess; private int _id; public int WindowMaxCount { get; } @@ -49,11 +49,7 @@ namespace Discord.Net.Rest public void Queue(RestRequest request) { - if (_destroyed) throw new Exception(); - //Assume this obj's parent is under lock - _queue.Enqueue(request); - Debug($"Request queued ({WindowCount}/{WindowMaxCount} + {_queue.Count})"); } public async Task ProcessQueue(bool acquireLock = false) { @@ -81,12 +77,17 @@ namespace Discord.Net.Rest try { - Stream stream; - if (request.IsMultipart) - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.MultipartParams, request.HeaderOnly).ConfigureAwait(false); + if (request.CancelToken.IsCancellationRequested) + request.Promise.SetException(new OperationCanceledException(request.CancelToken)); else - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.Json, request.HeaderOnly).ConfigureAwait(false); - request.Promise.SetResult(stream); + { + Stream stream; + if (request.IsMultipart) + stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.CancelToken, request.MultipartParams, request.HeaderOnly).ConfigureAwait(false); + else + stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.CancelToken, request.Json, request.HeaderOnly).ConfigureAwait(false); + request.Promise.SetResult(stream); + } } catch (HttpRateLimitException ex) //Preemptive check failed, use Discord's time instead of our own { @@ -94,17 +95,13 @@ namespace Discord.Net.Rest var task = _resetTask; if (task != null) { - Debug($"External rate limit: Extended to {ex.RetryAfterMilliseconds} ms"); var retryAfter = DateTime.UtcNow.AddMilliseconds(ex.RetryAfterMilliseconds); await task.ConfigureAwait(false); int millis = (int)Math.Ceiling((DateTime.UtcNow - retryAfter).TotalMilliseconds); _resetTask = ResetAfter(millis); } else - { - Debug($"External rate limit: Reset in {ex.RetryAfterMilliseconds} ms"); _resetTask = ResetAfter(ex.RetryAfterMilliseconds); - } return; } catch (HttpException ex) @@ -128,13 +125,11 @@ namespace Discord.Net.Rest _queue.TryDequeue(out request); WindowCount++; nextRetry = 1000; - Debug($"Request succeeded ({WindowCount}/{WindowMaxCount} + {_queue.Count})"); if (WindowCount == 1 && WindowSeconds > 0) { //First request for this window, schedule a reset _resetTask = ResetAfter(WindowSeconds * 1000); - Debug($"Internal rate limit: Reset in {WindowSeconds * 1000} ms"); } } @@ -145,11 +140,7 @@ namespace Discord.Net.Rest { await _parent.Lock().ConfigureAwait(false); if (_queue.IsEmpty) //Double check, in case a request was queued before we got both locks - { - Debug($"Destroy"); _parent.DestroyGuildBucket((GuildBucket)_bucketId, _guildId); - _destroyed = true; - } } finally { @@ -179,8 +170,6 @@ namespace Discord.Net.Rest { await Lock().ConfigureAwait(false); - Debug($"Reset"); - //Reset the current window count and set our state back to normal WindowCount = 0; _resetTask = null; @@ -188,10 +177,7 @@ namespace Discord.Net.Rest //Wait is over, work through the current queue await ProcessQueue().ConfigureAwait(false); } - finally - { - Unlock(); - } + finally { Unlock(); } } public async Task Lock() @@ -202,24 +188,5 @@ namespace Discord.Net.Rest { _lock.Release(); } - - //TODO: Remove - private void Debug(string text) - { - string name; - switch (_bucketGroup) - { - case BucketGroup.Global: - name = ((GlobalBucket)_bucketId).ToString(); - break; - case BucketGroup.Guild: - name = ((GuildBucket)_bucketId).ToString(); - break; - default: - name = "Unknown"; - break; - } - System.Diagnostics.Debug.WriteLine($"[{name} {_id}] {text}"); - } } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs b/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs index 098dccc8a..715333873 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs @@ -1,15 +1,17 @@ using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace Discord.Net.Rest { - internal struct RestRequest + internal class RestRequest { public string Method { get; } public string Endpoint { get; } public string Json { get; } public bool HeaderOnly { get; } + public CancellationToken CancelToken { get; internal set; } public IReadOnlyDictionary MultipartParams { get; } public TaskCompletionSource Promise { get; } diff --git a/src/Discord.Net/Net/Rest/RestClientProvider.cs b/src/Discord.Net/Net/Rest/RestClientProvider.cs index cf3ee0846..341d18f02 100644 --- a/src/Discord.Net/Net/Rest/RestClientProvider.cs +++ b/src/Discord.Net/Net/Rest/RestClientProvider.cs @@ -2,5 +2,5 @@ namespace Discord.Net.Rest { - public delegate IRestClient RestClientProvider(string baseUrl, CancellationToken cancelToken); + public delegate IRestClient RestClientProvider(string baseUrl); } diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index c719fab3e..504ff86f9 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -1,13 +1,10 @@ using Discord.API.Rest; using Discord.Logging; -using Discord.Net; using Discord.Net.Rest; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; @@ -43,7 +40,7 @@ namespace Discord.Rest _connectionLock = new SemaphoreSlim(1, 1); _log = new LogManager(config.LogLevel); _userAgent = DiscordConfig.UserAgent; - BaseClient = new API.DiscordRawClient(_restClientProvider, _cancelTokenSource.Token); + BaseClient = new API.DiscordRawClient(_restClientProvider); _log.Message += (s,e) => Log.Raise(this, e); } @@ -69,38 +66,43 @@ namespace Discord.Rest private async Task LoginInternal(string email, string password) { if (IsLoggedIn) - LogoutInternal(); + await LogoutInternal().ConfigureAwait(false); try { - var cancelTokenSource = new CancellationTokenSource(); + _cancelTokenSource = new CancellationTokenSource(); var args = new LoginParams { Email = email, Password = password }; - await BaseClient.Login(args).ConfigureAwait(false); - await CompleteLogin(cancelTokenSource, false).ConfigureAwait(false); + await BaseClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); + await CompleteLogin(false).ConfigureAwait(false); } - catch { LogoutInternal(); throw; } + catch { await LogoutInternal().ConfigureAwait(false); throw; } } private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) { if (IsLoggedIn) - LogoutInternal(); + await LogoutInternal().ConfigureAwait(false); try { - var cancelTokenSource = new CancellationTokenSource(); + _cancelTokenSource = new CancellationTokenSource(); - BaseClient.SetToken(tokenType, token); - await CompleteLogin(cancelTokenSource, validateToken).ConfigureAwait(false); + await BaseClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); + await CompleteLogin(validateToken).ConfigureAwait(false); } - catch { LogoutInternal(); throw; } + catch { await LogoutInternal().ConfigureAwait(false); throw; } } - private async Task CompleteLogin(CancellationTokenSource cancelTokenSource, bool validateToken) + private async Task CompleteLogin(bool validateToken) { BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); if (validateToken) - await BaseClient.ValidateToken().ConfigureAwait(false); - - _cancelTokenSource = cancelTokenSource; + { + try + { + await BaseClient.ValidateToken().ConfigureAwait(false); + } + catch { await BaseClient.Logout().ConfigureAwait(false); } + } + IsLoggedIn = true; LoggedIn.Raise(this); } @@ -111,11 +113,11 @@ namespace Discord.Rest await _connectionLock.WaitAsync().ConfigureAwait(false); try { - LogoutInternal(); + await LogoutInternal().ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private void LogoutInternal() + private async Task LogoutInternal() { bool wasLoggedIn = IsLoggedIn; @@ -125,7 +127,7 @@ namespace Discord.Rest catch { } } - BaseClient.SetToken(TokenType.User, null); + await BaseClient.Logout().ConfigureAwait(false); _currentUser = null; if (wasLoggedIn) diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs index 5d9220ade..d73d45a2e 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs @@ -1,7 +1,7 @@ using System; using Model = Discord.API.GuildEmbed; -namespace Discord.Rest +namespace Discord { public class GuildEmbed : IGuildEmbed { @@ -12,14 +12,11 @@ namespace Discord.Rest /// public ulong? ChannelId { get; private set; } - internal DiscordClient Discord { get; } - /// public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); - internal GuildEmbed(DiscordClient discord, Model model) + internal GuildEmbed(Model model) { - Discord = discord; Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs index c479f9f4d..5b2a83a78 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs @@ -82,6 +82,6 @@ namespace Discord.Rest IGuild IGuildIntegration.Guild => Guild; IRole IGuildIntegration.Role => Role; IUser IGuildIntegration.User => User; - IIntegrationAccount IGuildIntegration.Account => Account; + IntegrationAccount IGuildIntegration.Account => Account; } } diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs index cae71f5ae..596719d7b 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Model = Discord.API.UserGuild; -namespace Discord.Rest +namespace Discord { public class UserGuild : IUserGuild { @@ -10,7 +10,7 @@ namespace Discord.Rest /// public ulong Id { get; } - internal DiscordClient Discord { get; } + internal IDiscordClient Discord { get; } /// public string Name { get; private set; } @@ -22,7 +22,7 @@ namespace Discord.Rest /// public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); - internal UserGuild(DiscordClient discord, Model model) + internal UserGuild(IDiscordClient discord, Model model) { Discord = discord; Id = model.Id; @@ -40,15 +40,11 @@ namespace Discord.Rest /// public async Task Leave() { - if (IsOwner) - throw new InvalidOperationException("Unable to leave a guild the current user owns."); await Discord.BaseClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - if (!IsOwner) - throw new InvalidOperationException("Unable to delete a guild the current user does not own."); await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); } diff --git a/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs b/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs index 98087d694..71b0541e2 100644 --- a/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs +++ b/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs @@ -21,7 +21,7 @@ namespace Discord.Rest /// public int Uses { get; private set; } - internal override DiscordClient Discord => Guild.Discord; + internal override IDiscordClient Discord => Guild.Discord; internal GuildInvite(Guild guild, Model model) : base(model) diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index 24c3eb4df..fb74970bf 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -135,7 +135,6 @@ namespace Discord.Rest await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); } - public override string ToString() => $"{Author.ToString()}: {Text}"; IUser IMessage.Author => Author; diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index 9572c6620..eca5ff5cd 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -45,10 +45,7 @@ namespace Discord.Rest public async Task CreateDMChannel() { - var args = new CreateDMChannelParams - { - RecipientId = Id - }; + var args = new CreateDMChannelParams { RecipientId = Id }; var model = await Discord.BaseClient.CreateDMChannel(args).ConfigureAwait(false); return new DMChannel(Discord, model); diff --git a/src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs b/src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs new file mode 100644 index 000000000..4a79d23fc --- /dev/null +++ b/src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs @@ -0,0 +1,71 @@ +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.WebSocket +{ + internal struct ChannelMember + { + public GuildUser User { get; } + public ChannelPermissions Permissions { get; } + + public ChannelMember(GuildUser user, ChannelPermissions permissions) + { + User = user; + Permissions = permissions; + } + } + + internal class ChannelPermissionsCache + { + private readonly GuildChannel _channel; + private readonly ConcurrentDictionary _users; + + public IEnumerable Members => _users.Select(x => x.Value); + + public ChannelPermissionsCache(GuildChannel channel) + { + _channel = channel; + _users = new ConcurrentDictionary(1, (int)(_channel.Guild.UserCount * 1.05)); + } + + public ChannelMember? Get(ulong id) + { + ChannelMember member; + if (_users.TryGetValue(id, out member)) + return member; + return null; + } + public void Add(GuildUser user) + { + _users[user.Id] = new ChannelMember(user, new ChannelPermissions(PermissionHelper.Resolve(user, _channel))); + } + public void Remove(GuildUser user) + { + ChannelMember member; + _users.TryRemove(user.Id, out member); + } + + public void UpdateAll() + { + foreach (var pair in _users) + { + var member = pair.Value; + var newPerms = PermissionHelper.Resolve(member.User, _channel); + if (newPerms != member.Permissions.RawValue) + _users[pair.Key] = new ChannelMember(member.User, new ChannelPermissions(newPerms)); + } + } + public void Update(GuildUser user) + { + ChannelMember member; + if (_users.TryGetValue(user.Id, out member)) + { + var newPerms = PermissionHelper.Resolve(user, _channel); + if (newPerms != member.Permissions.RawValue) + _users[user.Id] = new ChannelMember(user, new ChannelPermissions(newPerms)); + } + } + } +} diff --git a/src/Discord.Net/WebSocket/Caches/MessageCache.cs b/src/Discord.Net/WebSocket/Caches/MessageCache.cs new file mode 100644 index 000000000..f3ef2b58b --- /dev/null +++ b/src/Discord.Net/WebSocket/Caches/MessageCache.cs @@ -0,0 +1,98 @@ +using Discord.API.Rest; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class MessageCache + { + private readonly DiscordClient _discord; + private readonly IMessageChannel _channel; + private readonly ConcurrentDictionary _messages; + private readonly ConcurrentQueue _orderedMessages; + private readonly int _size; + + public MessageCache(DiscordClient discord, IMessageChannel channel) + { + _discord = discord; + _channel = channel; + _size = discord.MessageCacheSize; + _messages = new ConcurrentDictionary(1, (int)(_size * 1.05)); + _orderedMessages = new ConcurrentQueue(); + } + + internal void Add(Message message) + { + if (_messages.TryAdd(message.Id, message)) + { + _orderedMessages.Enqueue(message.Id); + + ulong msgId; + Message msg; + while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out msgId)) + _messages.TryRemove(msgId, out msg); + } + } + + internal void Remove(ulong id) + { + Message msg; + _messages.TryRemove(id, out msg); + } + + public Message Get(ulong id) + { + Message result; + if (_messages.TryGetValue(id, out result)) + return result; + return null; + } + public async Task> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + { + //TODO: Test heavily + + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); + if (limit == 0) return ImmutableArray.Empty; + + IEnumerable cachedMessageIds; + if (fromMessageId == null) + cachedMessageIds = _orderedMessages; + else if (dir == Direction.Before) + cachedMessageIds = _orderedMessages.Where(x => x < fromMessageId.Value); + else + cachedMessageIds = _orderedMessages.Where(x => x > fromMessageId.Value); + + var cachedMessages = cachedMessageIds + .Take(limit) + .Select(x => + { + Message msg; + if (_messages.TryGetValue(x, out msg)) + return msg; + return null; + }) + .Where(x => x != null) + .ToArray(); + + if (cachedMessages.Length == limit) + return cachedMessages; + else if (cachedMessages.Length > limit) + return cachedMessages.Skip(cachedMessages.Length - limit); + else + { + var args = new GetChannelMessagesParams + { + Limit = limit - cachedMessages.Length, + RelativeDirection = dir, + RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Length - 1].Id + }; + var downloadedMessages = await _discord.BaseClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); + return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, x))).ToImmutableArray(); + } + } + } +} diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs new file mode 100644 index 000000000..4032a5539 --- /dev/null +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Discord.API; +using Discord.Net.Rest; + +namespace Discord.WebSocket +{ + public class DiscordClient : IDiscordClient + { + internal int MessageCacheSize { get; } = 100; + + public SelfUser CurrentUser + { + get + { + throw new NotImplementedException(); + } + } + + public TokenType AuthTokenType + { + get + { + throw new NotImplementedException(); + } + } + + public DiscordRawClient BaseClient + { + get + { + throw new NotImplementedException(); + } + } + + public IRequestQueue RequestQueue + { + get + { + throw new NotImplementedException(); + } + } + + public IRestClient RestClient + { + get + { + throw new NotImplementedException(); + } + } + + public Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) + { + throw new NotImplementedException(); + } + + public Task GetChannel(ulong id) + { + throw new NotImplementedException(); + } + + public Task> GetConnections() + { + throw new NotImplementedException(); + } + + public Task GetCurrentUser() + { + throw new NotImplementedException(); + } + + public Task> GetDMChannels() + { + throw new NotImplementedException(); + } + + public Task GetGuild(ulong id) + { + throw new NotImplementedException(); + } + + public Task> GetGuilds() + { + throw new NotImplementedException(); + } + + public Task GetInvite(string inviteIdOrXkcd) + { + throw new NotImplementedException(); + } + + public Task GetOptimalVoiceRegion() + { + throw new NotImplementedException(); + } + + public Task GetUser(ulong id) + { + throw new NotImplementedException(); + } + + public Task GetUser(string username, ushort discriminator) + { + throw new NotImplementedException(); + } + + public Task GetVoiceRegion(string id) + { + throw new NotImplementedException(); + } + + public Task> GetVoiceRegions() + { + throw new NotImplementedException(); + } + + public Task Login(string email, string password) + { + throw new NotImplementedException(); + } + + public Task Login(TokenType tokenType, string token, bool validateToken = true) + { + throw new NotImplementedException(); + } + + public Task Logout() + { + throw new NotImplementedException(); + } + + public Task> QueryUsers(string query, int limit) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs new file mode 100644 index 000000000..45d484d2a --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -0,0 +1,141 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + public class DMChannel : IDMChannel + { + private readonly MessageCache _messages; + + /// + public ulong Id { get; } + internal DiscordClient Discord { get; } + + /// + public DMUser Recipient { get; private set; } + + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + /// + public IEnumerable Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + + internal DMChannel(DiscordClient discord, Model model) + { + Id = model.Id; + Discord = discord; + _messages = new MessageCache(Discord, this); + + Update(model); + } + private void Update(Model model) + { + if (Recipient == null) + Recipient = new DMUser(this, model.Recipient); + else + Recipient.Update(model.Recipient); + } + + /// + public IUser GetUser(ulong id) + { + if (id == Recipient.Id) + return Recipient; + else if (id == Discord.CurrentUser.Id) + return Discord.CurrentUser; + else + return null; + } + + /// + public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) + { + return await _messages.GetMany(null, Direction.Before, limit).ConfigureAwait(false); + } + /// + public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + { + return await _messages.GetMany(fromMessageId, dir, limit).ConfigureAwait(false); + } + + /// + public async Task SendMessage(string text, bool isTTS = false) + { + var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.CreateMessage(Id, args).ConfigureAwait(false); + return new Message(this, model); + } + /// + public async Task SendFile(string filePath, string text = null, bool isTTS = false) + { + string filename = Path.GetFileName(filePath); + using (var file = File.OpenRead(filePath)) + { + var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.UploadFile(Id, file, args).ConfigureAwait(false); + return new Message(this, model); + } + } + /// + public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) + { + var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.UploadFile(Id, stream, args).ConfigureAwait(false); + return new Message(this, model); + } + + /// + public async Task DeleteMessages(IEnumerable messages) + { + await Discord.BaseClient.DeleteMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + } + + /// + public async Task TriggerTyping() + { + await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + } + + /// + public async Task Close() + { + await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + } + + /// + public async Task Update() + { + var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); + Update(model); + } + + /// + public override string ToString() => $"@{Recipient} [DM]"; + + IDMUser IDMChannel.Recipient => Recipient; + + Task> IChannel.GetUsers() + => Task.FromResult(Users); + Task IChannel.GetUser(ulong id) + => Task.FromResult(GetUser(id)); + Task IMessageChannel.GetMessage(ulong id) + => throw new NotSupportedException(); + async Task> IMessageChannel.GetMessages(int limit) + => await GetMessages(limit).ConfigureAwait(false); + async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) + => await GetMessages(fromMessageId, dir, limit).ConfigureAwait(false); + async Task IMessageChannel.SendMessage(string text, bool isTTS) + => await SendMessage(text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.SendFile(string filePath, string text, bool isTTS) + => await SendFile(filePath, text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.SendFile(Stream stream, string filename, string text, bool isTTS) + => await SendFile(stream, filename, text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.TriggerTyping() + => await TriggerTyping().ConfigureAwait(false); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs new file mode 100644 index 000000000..25d4ba619 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -0,0 +1,171 @@ +using Discord.API.Rest; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + public abstract class GuildChannel : IGuildChannel + { + private ConcurrentDictionary _overwrites; + private ChannelPermissionsCache _permissions; + + /// + public ulong Id { get; } + /// Gets the guild this channel is a member of. + public Guild Guild { get; } + + /// + public string Name { get; private set; } + /// + public int Position { get; private set; } + + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + /// + public IReadOnlyDictionary PermissionOverwrites => _overwrites; + internal DiscordClient Discord => Guild.Discord; + + internal GuildChannel(Guild guild, Model model) + { + Id = model.Id; + Guild = guild; + + Update(model); + } + internal virtual void Update(Model model) + { + Name = model.Name; + Position = model.Position; + + var newOverwrites = new ConcurrentDictionary(); + for (int i = 0; i < model.PermissionOverwrites.Length; i++) + { + var overwrite = model.PermissionOverwrites[i]; + newOverwrites[overwrite.TargetId] = new Overwrite(overwrite); + } + _overwrites = newOverwrites; + } + + public async Task Modify(Action func) + { + if (func != null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildChannelParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + Update(model); + } + + /// Gets a user in this channel with the given id. + public async Task GetUser(ulong id) + { + var model = await Discord.BaseClient.GetGuildMember(Guild.Id, id).ConfigureAwait(false); + if (model != null) + return new GuildUser(Guild, model); + return null; + } + protected abstract Task> GetUsers(); + + /// Gets the permission overwrite for a specific user, or null if one does not exist. + public OverwritePermissions? GetPermissionOverwrite(IUser user) + { + Overwrite value; + if (_overwrites.TryGetValue(Id, out value)) + return value.Permissions; + return null; + } + /// Gets the permission overwrite for a specific role, or null if one does not exist. + public OverwritePermissions? GetPermissionOverwrite(IRole role) + { + Overwrite value; + if (_overwrites.TryGetValue(Id, out value)) + return value.Permissions; + return null; + } + /// Downloads a collection of all invites to this channel. + public async Task> GetInvites() + { + var models = await Discord.BaseClient.GetChannelInvites(Id).ConfigureAwait(false); + return models.Select(x => new GuildInvite(Guild, x)); + } + + /// Adds or updates the permission overwrite for the given user. + public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) + { + var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; + await Discord.BaseClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); + _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); + } + /// Adds or updates the permission overwrite for the given role. + public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) + { + var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; + await Discord.BaseClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); + _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); + } + /// Removes the permission overwrite for the given user, if one exists. + public async Task RemovePermissionOverwrite(IUser user) + { + await Discord.BaseClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); + + Overwrite value; + _overwrites.TryRemove(user.Id, out value); + } + /// Removes the permission overwrite for the given role, if one exists. + public async Task RemovePermissionOverwrite(IRole role) + { + await Discord.BaseClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); + + Overwrite value; + _overwrites.TryRemove(role.Id, out value); + } + + /// Creates a new invite to this channel. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the guild after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + { + var args = new CreateChannelInviteParams + { + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + Temporary = isTemporary, + XkcdPass = withXkcd + }; + var model = await Discord.BaseClient.CreateChannelInvite(Id, args).ConfigureAwait(false); + return new GuildInvite(Guild, model); + } + + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + } + /// + public async Task Update() + { + var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); + Update(model); + } + + IGuild IGuildChannel.Guild => Guild; + async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); + async Task> IGuildChannel.GetInvites() + => await GetInvites().ConfigureAwait(false); + async Task> IGuildChannel.GetUsers() + => await GetUsers().ConfigureAwait(false); + async Task IGuildChannel.GetUser(ulong id) + => await GetUser(id).ConfigureAwait(false); + async Task> IChannel.GetUsers() + => await GetUsers().ConfigureAwait(false); + async Task IChannel.GetUser(ulong id) + => await GetUser(id).ConfigureAwait(false); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs new file mode 100644 index 000000000..2d1b0b818 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -0,0 +1,122 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + public class TextChannel : GuildChannel, ITextChannel + { + private readonly MessageCache _messages; + + /// + public string Topic { get; private set; } + + /// + public string Mention => MentionHelper.Mention(this); + + internal TextChannel(Guild guild, Model model) + : base(guild, model) + { + _messages = new MessageCache(Discord, this); + } + + internal override void Update(Model model) + { + Topic = model.Topic; + base.Update(model); + } + + public async Task Modify(Action func) + { + if (func != null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyTextChannelParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + Update(model); + } + + protected override async Task> GetUsers() + { + var users = await Guild.GetUsers().ConfigureAwait(false); + return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.ReadMessages)); + } + + /// + public Task GetMessage(ulong id) { throw new NotSupportedException(); } //Not implemented + /// + public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) + { + var args = new GetChannelMessagesParams { Limit = limit }; + var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + return models.Select(x => new Message(this, x)); + } + /// + public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + { + var args = new GetChannelMessagesParams { Limit = limit }; + var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + return models.Select(x => new Message(this, x)); + } + + /// + public async Task SendMessage(string text, bool isTTS = false) + { + var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); + return new Message(this, model); + } + /// + public async Task SendFile(string filePath, string text = null, bool isTTS = false) + { + string filename = Path.GetFileName(filePath); + using (var file = File.OpenRead(filePath)) + { + var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); + return new Message(this, model); + } + } + /// + public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) + { + var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; + var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); + return new Message(this, model); + } + + /// + public async Task DeleteMessages(IEnumerable messages) + { + await Discord.BaseClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + } + + /// + public async Task TriggerTyping() + { + await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + } + + /// + public override string ToString() => $"{base.ToString()} [Text]"; + + async Task IMessageChannel.GetMessage(ulong id) + => await GetMessage(id).ConfigureAwait(false); + async Task> IMessageChannel.GetMessages(int limit) + => await GetMessages(limit).ConfigureAwait(false); + async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) + => await GetMessages(fromMessageId, dir, limit).ConfigureAwait(false); + async Task IMessageChannel.SendMessage(string text, bool isTTS) + => await SendMessage(text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.SendFile(string filePath, string text, bool isTTS) + => await SendFile(filePath, text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.SendFile(Stream stream, string filename, string text, bool isTTS) + => await SendFile(stream, filename, text, isTTS).ConfigureAwait(false); + async Task IMessageChannel.TriggerTyping() + => await TriggerTyping().ConfigureAwait(false); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs new file mode 100644 index 000000000..e1819f9a9 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -0,0 +1,45 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Channel; + +namespace Discord.WebSocket +{ + public class VoiceChannel : GuildChannel, IVoiceChannel + { + /// + public int Bitrate { get; private set; } + + internal VoiceChannel(Guild guild, Model model) + : base(guild, model) + { + } + internal override void Update(Model model) + { + base.Update(model); + Bitrate = model.Bitrate; + } + + /// + public async Task Modify(Action func) + { + if (func != null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyVoiceChannelParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + Update(model); + } + + protected override async Task> GetUsers() + { + var users = await Guild.GetUsers().ConfigureAwait(false); + return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.Connect)); + } + + /// + public override string ToString() => $"{base.ToString()} [Voice]"; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs new file mode 100644 index 000000000..24c97e2d3 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -0,0 +1,374 @@ +using Discord.API.Rest; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Guild; +using EmbedModel = Discord.API.GuildEmbed; +using RoleModel = Discord.API.Role; + +namespace Discord.WebSocket +{ + /// Represents a Discord guild (called a server in the official client). + public class Guild : IGuild + { + private ConcurrentDictionary _roles; + private string _iconId, _splashId; + + /// + public ulong Id { get; } + internal DiscordClient Discord { get; } + + /// + public string Name { get; private set; } + /// + public int AFKTimeout { get; private set; } + /// + public bool IsEmbeddable { get; private set; } + /// + public int VerificationLevel { get; private set; } + public int UserCount { get; private set; } + + /// + public ulong? AFKChannelId { get; private set; } + /// + public ulong? EmbedChannelId { get; private set; } + /// + public ulong OwnerId { get; private set; } + /// + public string VoiceRegionId { get; private set; } + /// + public IReadOnlyList Emojis { get; private set; } + /// + public IReadOnlyList Features { get; private set; } + + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + /// + public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); + /// + public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId); + /// + public ulong DefaultChannelId => Id; + /// + public Role EveryoneRole => GetRole(Id); + /// Gets a collection of all roles in this guild. + public IEnumerable Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty(); + + internal Guild(DiscordClient discord, Model model) + { + Id = model.Id; + Discord = discord; + + Update(model); + } + private void Update(Model model) + { + AFKChannelId = model.AFKChannelId; + AFKTimeout = model.AFKTimeout; + EmbedChannelId = model.EmbedChannelId; + IsEmbeddable = model.EmbedEnabled; + Features = model.Features; + _iconId = model.Icon; + Name = model.Name; + OwnerId = model.OwnerId; + VoiceRegionId = model.Region; + _splashId = model.Splash; + VerificationLevel = model.VerificationLevel; + + if (model.Emojis != null) + { + var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); + for (int i = 0; i < model.Emojis.Length; i++) + emojis.Add(new Emoji(model.Emojis[i])); + Emojis = emojis.ToArray(); + } + else + Emojis = Array.Empty(); + + var roles = new ConcurrentDictionary(1, model.Roles?.Length ?? 0); + if (model.Roles != null) + { + for (int i = 0; i < model.Roles.Length; i++) + roles[model.Roles[i].Id] = new Role(this, model.Roles[i]); + } + _roles = roles; + } + private void Update(EmbedModel model) + { + IsEmbeddable = model.Enabled; + EmbedChannelId = model.ChannelId; + } + private void Update(IEnumerable models) + { + Role role; + foreach (var model in models) + { + if (_roles.TryGetValue(model.Id, out role)) + role.Update(model); + } + } + + /// + public async Task Update() + { + var response = await Discord.BaseClient.GetGuild(Id).ConfigureAwait(false); + Update(response); + } + /// + public async Task Modify(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuild(Id, args).ConfigureAwait(false); + Update(model); + } + /// + public async Task ModifyEmbed(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildEmbedParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); + + Update(model); + } + /// + public async Task ModifyChannels(IEnumerable args) + { + if (args == null) throw new NullReferenceException(nameof(args)); + + await Discord.BaseClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); + } + /// + public async Task ModifyRoles(IEnumerable args) + { + if (args == null) throw new NullReferenceException(nameof(args)); + + var models = await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); + Update(models); + } + /// + public async Task Leave() + { + await Discord.BaseClient.LeaveGuild(Id).ConfigureAwait(false); + } + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); + } + + /// + public async Task> GetBans() + { + var models = await Discord.BaseClient.GetGuildBans(Id).ConfigureAwait(false); + return models.Select(x => new PublicUser(Discord, x)); + } + /// + public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays); + /// + public async Task AddBan(ulong userId, int pruneDays = 0) + { + var args = new CreateGuildBanParams() + { + PruneDays = pruneDays + }; + await Discord.BaseClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); + } + /// + public Task RemoveBan(IUser user) => RemoveBan(user.Id); + /// + public async Task RemoveBan(ulong userId) + { + await Discord.BaseClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); + } + + /// Gets the channel in this guild with the provided id, or null if not found. + public async Task GetChannel(ulong id) + { + var model = await Discord.BaseClient.GetChannel(Id, id).ConfigureAwait(false); + if (model != null) + return ToChannel(model); + return null; + } + /// Gets a collection of all channels in this guild. + public async Task> GetChannels() + { + var models = await Discord.BaseClient.GetGuildChannels(Id).ConfigureAwait(false); + return models.Select(x => ToChannel(x)); + } + /// Creates a new text channel. + public async Task CreateTextChannel(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; + var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + return new TextChannel(this, model); + } + /// Creates a new voice channel. + public async Task CreateVoiceChannel(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; + var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + return new VoiceChannel(this, model); + } + + /// Gets a collection of all integrations attached to this guild. + public async Task> GetIntegrations() + { + var models = await Discord.BaseClient.GetGuildIntegrations(Id).ConfigureAwait(false); + return models.Select(x => new GuildIntegration(this, x)); + } + /// Creates a new integration for this guild. + public async Task CreateIntegration(ulong id, string type) + { + var args = new CreateGuildIntegrationParams { Id = id, Type = type }; + var model = await Discord.BaseClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); + return new GuildIntegration(this, model); + } + + /// Gets a collection of all invites to this guild. + public async Task> GetInvites() + { + var models = await Discord.BaseClient.GetGuildInvites(Id).ConfigureAwait(false); + return models.Select(x => new GuildInvite(this, x)); + } + /// Creates a new invite to this guild. + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + { + if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); + if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); + + var args = new CreateChannelInviteParams() + { + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + Temporary = isTemporary, + XkcdPass = withXkcd + }; + var model = await Discord.BaseClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); + return new GuildInvite(this, model); + } + + /// Gets the role in this guild with the provided id, or null if not found. + public Role GetRole(ulong id) + { + Role result = null; + if (_roles?.TryGetValue(id, out result) == true) + return result; + return null; + } + + /// Creates a new role. + public async Task CreateRole(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var model = await Discord.BaseClient.CreateGuildRole(Id).ConfigureAwait(false); + var role = new Role(this, model); + + await role.Modify(x => + { + x.Name = name; + x.Permissions = (permissions ?? role.Permissions).RawValue; + x.Color = (color ?? Color.Default).RawValue; + x.Hoist = isHoisted; + }).ConfigureAwait(false); + + return role; + } + + /// Gets a collection of all users in this guild. + public async Task> GetUsers() + { + var args = new GetGuildMembersParams(); + var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + return models.Select(x => new GuildUser(this, x)); + } + /// Gets a paged collection of all users in this guild. + public async Task> GetUsers(int limit, int offset) + { + var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; + var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + return models.Select(x => new GuildUser(this, x)); + } + /// Gets the user in this guild with the provided id, or null if not found. + public async Task GetUser(ulong id) + { + var model = await Discord.BaseClient.GetGuildMember(Id, id).ConfigureAwait(false); + if (model != null) + return new GuildUser(this, model); + return null; + } + /// Gets a the current user. + public async Task GetCurrentUser() + { + var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); + return await GetUser(currentUser.Id).ConfigureAwait(false); + } + public async Task PruneUsers(int days = 30, bool simulate = false) + { + var args = new GuildPruneParams() { Days = days }; + GetGuildPruneCountResponse model; + if (simulate) + model = await Discord.BaseClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); + else + model = await Discord.BaseClient.BeginGuildPrune(Id, args).ConfigureAwait(false); + return model.Pruned; + } + + internal GuildChannel ToChannel(API.Channel model) + { + switch (model.Type) + { + case ChannelType.Text: + default: + return new TextChannel(this, model); + case ChannelType.Voice: + return new VoiceChannel(this, model); + } + } + + public override string ToString() => Name ?? Id.ToString(); + + IEnumerable IGuild.Emojis => Emojis; + ulong IGuild.EveryoneRoleId => EveryoneRole.Id; + IEnumerable IGuild.Features => Features; + + async Task> IGuild.GetBans() + => await GetBans().ConfigureAwait(false); + async Task IGuild.GetChannel(ulong id) + => await GetChannel(id).ConfigureAwait(false); + async Task> IGuild.GetChannels() + => await GetChannels().ConfigureAwait(false); + async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); + async Task IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) + => await CreateRole(name, permissions, color, isHoisted).ConfigureAwait(false); + async Task IGuild.CreateTextChannel(string name) + => await CreateTextChannel(name).ConfigureAwait(false); + async Task IGuild.CreateVoiceChannel(string name) + => await CreateVoiceChannel(name).ConfigureAwait(false); + async Task> IGuild.GetInvites() + => await GetInvites().ConfigureAwait(false); + Task IGuild.GetRole(ulong id) + => Task.FromResult(GetRole(id)); + Task> IGuild.GetRoles() + => Task.FromResult>(Roles); + async Task IGuild.GetUser(ulong id) + => await GetUser(id).ConfigureAwait(false); + async Task IGuild.GetCurrentUser() + => await GetCurrentUser().ConfigureAwait(false); + async Task> IGuild.GetUsers() + => await GetUsers().ConfigureAwait(false); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs new file mode 100644 index 000000000..d140618ed --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs @@ -0,0 +1,87 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; +using Model = Discord.API.Integration; + +namespace Discord.WebSocket +{ + public class GuildIntegration : IGuildIntegration + { + /// + public ulong Id { get; private set; } + /// + public string Name { get; private set; } + /// + public string Type { get; private set; } + /// + public bool IsEnabled { get; private set; } + /// + public bool IsSyncing { get; private set; } + /// + public ulong ExpireBehavior { get; private set; } + /// + public ulong ExpireGracePeriod { get; private set; } + /// + public DateTime SyncedAt { get; private set; } + + /// + public Guild Guild { get; private set; } + /// + public Role Role { get; private set; } + /// + public User User { get; private set; } + /// + public IntegrationAccount Account { get; private set; } + internal DiscordClient Discord => Guild.Discord; + + internal GuildIntegration(Guild guild, Model model) + { + Guild = guild; + Update(model); + } + + private void Update(Model model) + { + Id = model.Id; + Name = model.Name; + Type = model.Type; + IsEnabled = model.Enabled; + IsSyncing = model.Syncing; + ExpireBehavior = model.ExpireBehavior; + ExpireGracePeriod = model.ExpireGracePeriod; + SyncedAt = model.SyncedAt; + + Role = Guild.GetRole(model.RoleId); + User = new PublicUser(Discord, model.User); + } + + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + } + /// + public async Task Modify(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildIntegrationParams(); + func(args); + var model = await Discord.BaseClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); + + Update(model); + } + /// + public async Task Sync() + { + await Discord.BaseClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + } + + public override string ToString() => $"{Name ?? Id.ToString()} ({(IsEnabled ? "Enabled" : "Disabled")})"; + + IGuild IGuildIntegration.Guild => Guild; + IRole IGuildIntegration.Role => Role; + IUser IGuildIntegration.User => User; + IntegrationAccount IGuildIntegration.Account => Account; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs b/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs new file mode 100644 index 000000000..c78c7a9e9 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks; +using Model = Discord.API.InviteMetadata; + +namespace Discord.WebSocket +{ + public class GuildInvite : Invite, IGuildInvite + { + /// Gets the guild this invite is linked to. + public Guild Guild { get; private set; } + /// + public ulong ChannelId { get; private set; } + + /// + public bool IsRevoked { get; private set; } + /// + public bool IsTemporary { get; private set; } + /// + public int? MaxAge { get; private set; } + /// + public int? MaxUses { get; private set; } + /// + public int Uses { get; private set; } + + internal override IDiscordClient Discord => Guild.Discord; + + internal GuildInvite(Guild guild, Model model) + : base(model) + { + Guild = guild; + + Update(model); //Causes base.Update(Model) to be run twice, but that's fine. + } + private void Update(Model model) + { + base.Update(model); + IsRevoked = model.Revoked; + IsTemporary = model.Temporary; + MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; + MaxUses = model.MaxUses; + Uses = model.Uses; + } + + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteInvite(Code).ConfigureAwait(false); + } + + IGuild IGuildInvite.Guild => Guild; + ulong IInvite.GuildId => Guild.Id; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs new file mode 100644 index 000000000..1b7d39732 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -0,0 +1,146 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Model = Discord.API.Message; + +namespace Discord.WebSocket +{ + public class Message : IMessage + { + /// + public ulong Id { get; } + + /// + public DateTime? EditedTimestamp { get; private set; } + /// + public bool IsTTS { get; private set; } + /// + public string RawText { get; private set; } + /// + public string Text { get; private set; } + /// + public DateTime Timestamp { get; private set; } + + /// + public IMessageChannel Channel { get; } + /// + public User Author { get; } + + /// + public IReadOnlyList Attachments { get; private set; } + /// + public IReadOnlyList Embeds { get; private set; } + /// + public IReadOnlyList MentionedUsers { get; private set; } + /// + public IReadOnlyList MentionedChannelIds { get; private set; } + /// + public IReadOnlyList MentionedRoleIds { get; private set; } + + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; + + internal Message(IMessageChannel channel, Model model) + { + Id = model.Id; + Channel = channel; + Author = new PublicUser(Discord, model.Author); + + Update(model); + } + private void Update(Model model) + { + IsTTS = model.IsTextToSpeech; + Timestamp = model.Timestamp; + EditedTimestamp = model.EditedTimestamp; + RawText = model.Content; + + if (model.Attachments.Length > 0) + { + var attachments = new Attachment[model.Attachments.Length]; + for (int i = 0; i < attachments.Length; i++) + attachments[i] = new Attachment(model.Attachments[i]); + Attachments = ImmutableArray.Create(attachments); + } + else + Attachments = Array.Empty(); + + if (model.Embeds.Length > 0) + { + var embeds = new Embed[model.Attachments.Length]; + for (int i = 0; i < embeds.Length; i++) + embeds[i] = new Embed(model.Embeds[i]); + Embeds = ImmutableArray.Create(embeds); + } + else + Embeds = Array.Empty(); + + if (model.Mentions.Length > 0) + { + var discord = Discord; + var builder = ImmutableArray.CreateBuilder(model.Mentions.Length); + for (int i = 0; i < model.Mentions.Length; i++) + builder.Add(new PublicUser(discord, model.Mentions[i])); + MentionedUsers = builder.ToArray(); + } + else + MentionedUsers = Array.Empty(); + MentionedChannelIds = MentionHelper.GetChannelMentions(model.Content); + MentionedRoleIds = MentionHelper.GetRoleMentions(model.Content); + if (model.IsMentioningEveryone) + { + ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; + if (guildId != null) + { + if (MentionedRoleIds.Count == 0) + MentionedRoleIds = ImmutableArray.Create(guildId.Value); + else + { + var builder = ImmutableArray.CreateBuilder(MentionedRoleIds.Count + 1); + builder.AddRange(MentionedRoleIds); + builder.Add(guildId.Value); + MentionedRoleIds = builder.ToImmutable(); + } + } + } + + Text = MentionHelper.CleanUserMentions(model.Content, model.Mentions); + + Author.Update(model.Author); + } + + /// + public async Task Modify(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyMessageParams(); + func(args); + var guildChannel = Channel as GuildChannel; + + Model model; + if (guildChannel != null) + model = await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + else + model = await Discord.BaseClient.ModifyMessage(Channel.Id, Id, args).ConfigureAwait(false); + Update(model); + } + + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); + } + + public override string ToString() => $"{Author.ToString()}: {Text}"; + + IUser IMessage.Author => Author; + IReadOnlyList IMessage.Attachments => Attachments; + IReadOnlyList IMessage.Embeds => Embeds; + IReadOnlyList IMessage.MentionedChannelIds => MentionedChannelIds; + IReadOnlyList IMessage.MentionedUsers => MentionedUsers; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs new file mode 100644 index 000000000..a48d8fee8 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -0,0 +1,80 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.Role; + +namespace Discord.WebSocket +{ + public class Role : IRole, IMentionable + { + /// + public ulong Id { get; } + /// Returns the guild this role belongs to. + public Guild Guild { get; } + + /// + public Color Color { get; private set; } + /// + public bool IsHoisted { get; private set; } + /// + public bool IsManaged { get; private set; } + /// + public string Name { get; private set; } + /// + public GuildPermissions Permissions { get; private set; } + /// + public int Position { get; private set; } + + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + /// + public bool IsEveryone => Id == Guild.Id; + /// + public string Mention => MentionHelper.Mention(this); + internal DiscordClient Discord => Guild.Discord; + + internal Role(Guild guild, Model model) + { + Id = model.Id; + Guild = guild; + + Update(model); + } + internal void Update(Model model) + { + Name = model.Name; + IsHoisted = model.Hoist.Value; + IsManaged = model.Managed.Value; + Position = model.Position.Value; + Color = new Color(model.Color.Value); + Permissions = new GuildPermissions(model.Permissions.Value); + } + /// Modifies the properties of this role. + public async Task Modify(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildRoleParams(); + func(args); + var response = await Discord.BaseClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); + Update(response); + } + /// Deletes this message. + public async Task Delete() + => await Discord.BaseClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); + + /// + public override string ToString() => Name ?? Id.ToString(); + + ulong IRole.GuildId => Guild.Id; + + async Task> IRole.GetUsers() + { + //A tad hacky, but it works + var models = await Discord.BaseClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); + return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); + } + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs b/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs new file mode 100644 index 000000000..4b300ce76 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs @@ -0,0 +1,20 @@ +using Model = Discord.API.User; + +namespace Discord.WebSocket +{ + public class DMUser : User, IDMUser + { + /// + public DMChannel Channel { get; } + + internal override DiscordClient Discord => Channel.Discord; + + internal DMUser(DMChannel channel, Model model) + : base(model) + { + Channel = channel; + } + + IDMChannel IDMUser.Channel => Channel; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs new file mode 100644 index 000000000..40d47d2f3 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -0,0 +1,117 @@ +using Discord.API.Rest; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Model = Discord.API.GuildMember; + +namespace Discord.WebSocket +{ + public class GuildUser : User, IGuildUser + { + private ImmutableArray _roles; + + public Guild Guild { get; } + + /// + public bool IsDeaf { get; private set; } + /// + public bool IsMute { get; private set; } + /// + public DateTime JoinedAt { get; private set; } + /// + public string Nickname { get; private set; } + + /// + public IReadOnlyList Roles => _roles; + internal override DiscordClient Discord => Guild.Discord; + + internal GuildUser(Guild guild, Model model) + : base(model.User) + { + Guild = guild; + } + internal void Update(Model model) + { + IsDeaf = model.Deaf; + IsMute = model.Mute; + JoinedAt = model.JoinedAt.Value; + Nickname = model.Nick; + + var roles = ImmutableArray.CreateBuilder(model.Roles.Length + 1); + roles.Add(Guild.EveryoneRole); + for (int i = 0; i < model.Roles.Length; i++) + roles.Add(Guild.GetRole(model.Roles[i])); + _roles = roles.ToImmutable(); + } + + public async Task Update() + { + var model = await Discord.BaseClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); + Update(model); + } + + public bool HasRole(IRole role) + { + for (int i = 0; i < _roles.Length; i++) + { + if (_roles[i].Id == role.Id) + return true; + } + return false; + } + + public async Task Kick() + { + await Discord.BaseClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); + } + + public GuildPermissions GetGuildPermissions() + { + return new GuildPermissions(PermissionHelper.Resolve(this)); + } + public ChannelPermissions GetPermissions(IGuildChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return new ChannelPermissions(PermissionHelper.Resolve(this, channel)); + } + + public async Task Modify(Action func) + { + if (func == null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyGuildMemberParams(); + func(args); + + bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; + if (isCurrentUser && args.Nickname.IsSpecified) + { + var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; + await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); + args.Nickname = new API.Optional(); //Remove + } + + if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) + { + await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + if (args.Deaf.IsSpecified) + IsDeaf = args.Deaf; + if (args.Mute.IsSpecified) + IsMute = args.Mute; + if (args.Nickname.IsSpecified) + Nickname = args.Nickname; + if (args.Roles.IsSpecified) + _roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); + } + } + + + IGuild IGuildUser.Guild => Guild; + IReadOnlyList IGuildUser.Roles => Roles; + ulong? IGuildUser.VoiceChannelId => null; + + ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) + => GetPermissions(channel); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs b/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs new file mode 100644 index 000000000..f1816077e --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs @@ -0,0 +1,15 @@ +using Model = Discord.API.User; + +namespace Discord.WebSocket +{ + public class PublicUser : User + { + internal override DiscordClient Discord { get; } + + internal PublicUser(DiscordClient discord, Model model) + : base(model) + { + Discord = discord; + } + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs new file mode 100644 index 000000000..f7e27b130 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs @@ -0,0 +1,48 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.WebSocket +{ + public class SelfUser : User, ISelfUser + { + internal override DiscordClient Discord { get; } + + /// + public string Email { get; private set; } + /// + public bool IsVerified { get; private set; } + + internal SelfUser(DiscordClient discord, Model model) + : base(model) + { + Discord = discord; + } + internal override void Update(Model model) + { + base.Update(model); + + Email = model.Email; + IsVerified = model.IsVerified; + } + + /// + public async Task Update() + { + var model = await Discord.BaseClient.GetCurrentUser().ConfigureAwait(false); + Update(model); + } + + /// + public async Task Modify(Action func) + { + if (func != null) throw new NullReferenceException(nameof(func)); + + var args = new ModifyCurrentUserParams(); + func(args); + var model = await Discord.BaseClient.ModifyCurrentUser(args).ConfigureAwait(false); + Update(model); + } + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs new file mode 100644 index 000000000..6cda730c4 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -0,0 +1,65 @@ +using Discord.API.Rest; +using System; +using System.Threading.Tasks; +using Model = Discord.API.User; + +namespace Discord.WebSocket +{ + public abstract class User : IUser + { + private string _avatarId; + + /// + public ulong Id { get; } + internal abstract DiscordClient Discord { get; } + + /// + public ushort Discriminator { get; private set; } + /// + public bool IsBot { get; private set; } + /// + public string Username { get; private set; } + + /// + public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); + /// + public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + /// + public string Mention => MentionHelper.Mention(this, false); + /// + public string NicknameMention => MentionHelper.Mention(this, true); + + internal User(Model model) + { + Id = model.Id; + + Update(model); + } + internal virtual void Update(Model model) + { + _avatarId = model.Avatar; + Discriminator = model.Discriminator; + IsBot = model.Bot; + Username = model.Username; + } + + public async Task CreateDMChannel() + { + var args = new CreateDMChannelParams { RecipientId = Id }; + var model = await Discord.BaseClient.CreateDMChannel(args).ConfigureAwait(false); + + return new DMChannel(Discord, model); + } + + public override string ToString() => $"{Username ?? Id.ToString()}"; + + /// + string IUser.CurrentGame => null; + /// + UserStatus IUser.Status => UserStatus.Unknown; + + /// + async Task IUser.CreateDMChannel() + => await CreateDMChannel().ConfigureAwait(false); + } +} From f5045f2a679ef88c660f9ce6764f8e861fb1f7a8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 21:03:29 -0300 Subject: [PATCH 02/39] Added Game model --- src/Discord.Net/API/Common/Game.cs | 14 ++++++++++++++ src/Discord.Net/Common/Entities/Users/Game.cs | 16 ++++++++++++++++ src/Discord.Net/Common/Entities/Users/IUser.cs | 2 +- .../Common/Entities/Users/StreamType.cs | 8 ++++++++ src/Discord.Net/Discord.Net.csproj | 3 +++ src/Discord.Net/Rest/Entities/Users/User.cs | 2 +- src/Discord.Net/WebSocket/Entities/Users/User.cs | 2 +- 7 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/Discord.Net/API/Common/Game.cs create mode 100644 src/Discord.Net/Common/Entities/Users/Game.cs create mode 100644 src/Discord.Net/Common/Entities/Users/StreamType.cs diff --git a/src/Discord.Net/API/Common/Game.cs b/src/Discord.Net/API/Common/Game.cs new file mode 100644 index 000000000..a5bbbfcdc --- /dev/null +++ b/src/Discord.Net/API/Common/Game.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class Game + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("url")] + public string StreamUrl { get; set; } + [JsonProperty("type")] + public StreamType StreamType { get; set; } + } +} diff --git a/src/Discord.Net/Common/Entities/Users/Game.cs b/src/Discord.Net/Common/Entities/Users/Game.cs new file mode 100644 index 000000000..51d9c0b18 --- /dev/null +++ b/src/Discord.Net/Common/Entities/Users/Game.cs @@ -0,0 +1,16 @@ +namespace Discord +{ + public struct Game + { + public string Name { get; } + public string StreamUrl { get; } + public StreamType StreamType { get; } + + public Game(string name, string streamUrl, StreamType type) + { + Name = name; + StreamUrl = streamUrl; + StreamType = type; + } + } +} diff --git a/src/Discord.Net/Common/Entities/Users/IUser.cs b/src/Discord.Net/Common/Entities/Users/IUser.cs index 550c53e8b..9808c4a9f 100644 --- a/src/Discord.Net/Common/Entities/Users/IUser.cs +++ b/src/Discord.Net/Common/Entities/Users/IUser.cs @@ -7,7 +7,7 @@ namespace Discord /// Gets the url to this user's avatar. string AvatarUrl { get; } /// Gets the game this user is currently playing, if any. - string CurrentGame { get; } + Game? CurrentGame { get; } /// Gets the per-username unique id for this user. ushort Discriminator { get; } /// Returns true if this user is a bot account. diff --git a/src/Discord.Net/Common/Entities/Users/StreamType.cs b/src/Discord.Net/Common/Entities/Users/StreamType.cs new file mode 100644 index 000000000..7622e3d6e --- /dev/null +++ b/src/Discord.Net/Common/Entities/Users/StreamType.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum StreamType + { + NotStreaming = 0, + Twitch = 1 + } +} diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 88794a5aa..0d8fb6709 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -49,6 +49,7 @@ + @@ -100,6 +101,8 @@ + + diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index eca5ff5cd..0fe02bacc 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -54,7 +54,7 @@ namespace Discord.Rest public override string ToString() => $"{Username ?? Id.ToString()}"; /// - string IUser.CurrentGame => null; + Game? IUser.CurrentGame => null; /// UserStatus IUser.Status => UserStatus.Unknown; diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index 6cda730c4..b757cd003 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -54,7 +54,7 @@ namespace Discord.WebSocket public override string ToString() => $"{Username ?? Id.ToString()}"; /// - string IUser.CurrentGame => null; + Game? IUser.CurrentGame => null; /// UserStatus IUser.Status => UserStatus.Unknown; From 57dfd93973881835c6bd6c7b272178fe5941ad23 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 21:26:24 -0300 Subject: [PATCH 03/39] Remove IUpdateable.Update from WebSocket entities --- src/Discord.Net/Discord.Net.csproj | 4 +-- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 6 +---- .../{Caches => }/ChannelPermissionsCache.cs | 0 .../WebSocket/Entities/Channels/DMChannel.cs | 9 ++----- .../Entities/Channels/GuildChannel.cs | 9 ++----- .../Entities/Channels/TextChannel.cs | 3 +-- .../Entities/Channels/VoiceChannel.cs | 3 +-- .../WebSocket/Entities/Guilds/Guild.cs | 26 +++++-------------- .../Entities/Guilds/GuildIntegration.cs | 4 +-- src/Discord.Net/WebSocket/Entities/Message.cs | 8 +++--- src/Discord.Net/WebSocket/Entities/Role.cs | 3 +-- .../WebSocket/Entities/Users/GuildUser.cs | 8 ++---- .../WebSocket/Entities/Users/SelfUser.cs | 15 ++++------- .../WebSocket/{Caches => }/MessageCache.cs | 0 14 files changed, 28 insertions(+), 70 deletions(-) rename src/Discord.Net/WebSocket/{Caches => }/ChannelPermissionsCache.cs (100%) rename src/Discord.Net/WebSocket/{Caches => }/MessageCache.cs (100%) diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 0d8fb6709..0c5742f1c 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -208,8 +208,8 @@ - - + + diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index 5763193bc..d68ff73c6 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -140,15 +140,11 @@ namespace Discord.Rest /// public async Task ModifyChannels(IEnumerable args) { - if (args == null) throw new NullReferenceException(nameof(args)); - await Discord.BaseClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) - { - if (args == null) throw new NullReferenceException(nameof(args)); - + { var models = await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); Update(models); } diff --git a/src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs b/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs similarity index 100% rename from src/Discord.Net/WebSocket/Caches/ChannelPermissionsCache.cs rename to src/Discord.Net/WebSocket/ChannelPermissionsCache.cs diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index 45d484d2a..cb5294da6 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -107,13 +107,6 @@ namespace Discord.WebSocket await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); } - /// - public async Task Update() - { - var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); - Update(model); - } - /// public override string ToString() => $"@{Recipient} [DM]"; @@ -137,5 +130,7 @@ namespace Discord.WebSocket => await SendFile(stream, filename, text, isTTS).ConfigureAwait(false); async Task IMessageChannel.TriggerTyping() => await TriggerTyping().ConfigureAwait(false); + Task IUpdateable.Update() + => Task.CompletedTask; } } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 25d4ba619..645eec5f3 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -57,7 +57,6 @@ namespace Discord.WebSocket var args = new ModifyGuildChannelParams(); func(args); var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); - Update(model); } /// Gets a user in this channel with the given id. @@ -147,12 +146,6 @@ namespace Discord.WebSocket { await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); } - /// - public async Task Update() - { - var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); - Update(model); - } IGuild IGuildChannel.Guild => Guild; async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) @@ -167,5 +160,7 @@ namespace Discord.WebSocket => await GetUsers().ConfigureAwait(false); async Task IChannel.GetUser(ulong id) => await GetUser(id).ConfigureAwait(false); + Task IUpdateable.Update() + => Task.CompletedTask; } } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index 2d1b0b818..23b3c2f70 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -36,8 +36,7 @@ namespace Discord.WebSocket var args = new ModifyTextChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } protected override async Task> GetUsers() diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index e1819f9a9..286a67520 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -29,8 +29,7 @@ namespace Discord.WebSocket var args = new ModifyVoiceChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } protected override async Task> GetUsers() diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 24c97e2d3..95c4a1413 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -110,13 +110,7 @@ namespace Discord.WebSocket role.Update(model); } } - - /// - public async Task Update() - { - var response = await Discord.BaseClient.GetGuild(Id).ConfigureAwait(false); - Update(response); - } + /// public async Task Modify(Action func) { @@ -124,8 +118,7 @@ namespace Discord.WebSocket var args = new ModifyGuildParams(); func(args); - var model = await Discord.BaseClient.ModifyGuild(Id, args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyGuild(Id, args).ConfigureAwait(false); } /// public async Task ModifyEmbed(Action func) @@ -134,24 +127,17 @@ namespace Discord.WebSocket var args = new ModifyGuildEmbedParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); - - Update(model); + await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); } /// public async Task ModifyChannels(IEnumerable args) { - if (args == null) throw new NullReferenceException(nameof(args)); - await Discord.BaseClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) - { - if (args == null) throw new NullReferenceException(nameof(args)); - - var models = await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); - Update(models); + { + await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); } /// public async Task Leave() @@ -370,5 +356,7 @@ namespace Discord.WebSocket => await GetCurrentUser().ConfigureAwait(false); async Task> IGuild.GetUsers() => await GetUsers().ConfigureAwait(false); + Task IUpdateable.Update() + => Task.CompletedTask; } } diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs index d140618ed..14c8a4911 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs @@ -67,9 +67,7 @@ namespace Discord.WebSocket var args = new ModifyGuildIntegrationParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); - - Update(model); + await Discord.BaseClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); } /// public async Task Sync() diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index 1b7d39732..5ab17ac5d 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -120,13 +120,11 @@ namespace Discord.WebSocket var args = new ModifyMessageParams(); func(args); var guildChannel = Channel as GuildChannel; - - Model model; + if (guildChannel != null) - model = await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - model = await Discord.BaseClient.ModifyMessage(Channel.Id, Id, args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyMessage(Channel.Id, Id, args).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs index a48d8fee8..9a8ca2bcc 100644 --- a/src/Discord.Net/WebSocket/Entities/Role.cs +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -58,8 +58,7 @@ namespace Discord.WebSocket var args = new ModifyGuildRoleParams(); func(args); - var response = await Discord.BaseClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); - Update(response); + await Discord.BaseClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); } /// Deletes this message. public async Task Delete() diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 40d47d2f3..614476b5b 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -46,12 +46,6 @@ namespace Discord.WebSocket _roles = roles.ToImmutable(); } - public async Task Update() - { - var model = await Discord.BaseClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); - Update(model); - } - public bool HasRole(IRole role) { for (int i = 0; i < _roles.Length; i++) @@ -113,5 +107,7 @@ namespace Discord.WebSocket ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => GetPermissions(channel); + Task IUpdateable.Update() + => Task.CompletedTask; } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs index f7e27b130..b852fbc15 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs @@ -26,14 +26,7 @@ namespace Discord.WebSocket Email = model.Email; IsVerified = model.IsVerified; } - - /// - public async Task Update() - { - var model = await Discord.BaseClient.GetCurrentUser().ConfigureAwait(false); - Update(model); - } - + /// public async Task Modify(Action func) { @@ -41,8 +34,10 @@ namespace Discord.WebSocket var args = new ModifyCurrentUserParams(); func(args); - var model = await Discord.BaseClient.ModifyCurrentUser(args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyCurrentUser(args).ConfigureAwait(false); } + + Task IUpdateable.Update() + => Task.CompletedTask; } } diff --git a/src/Discord.Net/WebSocket/Caches/MessageCache.cs b/src/Discord.Net/WebSocket/MessageCache.cs similarity index 100% rename from src/Discord.Net/WebSocket/Caches/MessageCache.cs rename to src/Discord.Net/WebSocket/MessageCache.cs From 07e8eb45813a01de98900fb9135c75879afd90be Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 22:47:13 -0300 Subject: [PATCH 04/39] Fixed several channel bugs and cleaned up WS channel entities. --- .../Entities/Channels/IMessageChannel.cs | 8 ++- .../Common/Entities/Users/IGuildUser.cs | 4 +- .../Rest/Entities/Channels/DMChannel.cs | 5 +- .../Rest/Entities/Channels/GuildChannel.cs | 23 ++++----- .../Rest/Entities/Channels/TextChannel.cs | 19 ++++--- .../Rest/Entities/Channels/VoiceChannel.cs | 8 +-- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 1 - .../Rest/Entities/Users/GuildUser.cs | 2 +- .../WebSocket/Entities/Channels/DMChannel.cs | 19 ++++--- .../Entities/Channels/GuildChannel.cs | 50 +++++++------------ .../Entities/Channels/TextChannel.cs | 39 +++++++++------ .../Entities/Channels/VoiceChannel.cs | 11 ++-- .../WebSocket/Entities/Guilds/Guild.cs | 1 + .../WebSocket/Entities/Users/GuildUser.cs | 4 +- src/Discord.Net/WebSocket/MessageCache.cs | 31 ++++++++---- 15 files changed, 122 insertions(+), 103 deletions(-) diff --git a/src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs b/src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs index c2a10f30b..e0613da48 100644 --- a/src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs @@ -6,8 +6,12 @@ namespace Discord { public interface IMessageChannel : IChannel { - /// Gets the message in this message channel with the given id, or null if none was found. - Task GetMessage(ulong id); + /// Gets all messages in this channel's cache. + IEnumerable CachedMessages { get; } + + /// Gets the message from this channel's cache with the given id, or null if none was found. + Task GetCachedMessage(ulong id); + /// Gets the last N messages from this message channel. Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch); /// Gets a collection of messages in this channel. diff --git a/src/Discord.Net/Common/Entities/Users/IGuildUser.cs b/src/Discord.Net/Common/Entities/Users/IGuildUser.cs index 222530ecd..5e038909e 100644 --- a/src/Discord.Net/Common/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net/Common/Entities/Users/IGuildUser.cs @@ -21,8 +21,8 @@ namespace Discord IGuild Guild { get; } /// Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. IReadOnlyList Roles { get; } - /// Gets the id of the voice channel this user is currently in, if any. - ulong? VoiceChannelId { get; } + /// Gets the voice channel this user is currently in, if any. + IVoiceChannel VoiceChannel { get; } /// Gets the guild-level permissions granted to this user by their roles. GuildPermissions GetGuildPermissions(); diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index 3a76aa702..57e684493 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -124,13 +124,14 @@ namespace Discord.Rest public override string ToString() => $"@{Recipient} [DM]"; IDMUser IDMChannel.Recipient => Recipient; + IEnumerable IMessageChannel.CachedMessages => Array.Empty(); async Task> IChannel.GetUsers() => await GetUsers().ConfigureAwait(false); async Task IChannel.GetUser(ulong id) => await GetUser(id).ConfigureAwait(false); - Task IMessageChannel.GetMessage(ulong id) - => throw new NotSupportedException(); + Task IMessageChannel.GetCachedMessage(ulong id) + => Task.FromResult(null); async Task> IMessageChannel.GetMessages(int limit) => await GetMessages(limit).ConfigureAwait(false); async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index c14e918fe..c06cb8e70 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -60,16 +60,11 @@ namespace Discord.Rest } /// Gets a user in this channel with the given id. - public async Task GetUser(ulong id) - { - var model = await Discord.BaseClient.GetGuildMember(Guild.Id, id).ConfigureAwait(false); - if (model != null) - return new GuildUser(Guild, model); - return null; - } - protected abstract Task> GetUsers(); + public abstract Task GetUser(ulong id); + /// Gets all users in this channel. + public abstract Task> GetUsers(); - /// Gets the permission overwrite for a specific user, or null if one does not exist. + /// public OverwritePermissions? GetPermissionOverwrite(IUser user) { Overwrite value; @@ -77,7 +72,7 @@ namespace Discord.Rest return value.Permissions; return null; } - /// Gets the permission overwrite for a specific role, or null if one does not exist. + /// public OverwritePermissions? GetPermissionOverwrite(IRole role) { Overwrite value; @@ -92,21 +87,21 @@ namespace Discord.Rest return models.Select(x => new GuildInvite(Guild, x)); } - /// Adds or updates the permission overwrite for the given user. + /// public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; await Discord.BaseClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); } - /// Adds or updates the permission overwrite for the given role. + /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; await Discord.BaseClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); } - /// Removes the permission overwrite for the given user, if one exists. + /// public async Task RemovePermissionOverwrite(IUser user) { await Discord.BaseClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); @@ -114,7 +109,7 @@ namespace Discord.Rest Overwrite value; _overwrites.TryRemove(user.Id, out value); } - /// Removes the permission overwrite for the given role, if one exists. + /// public async Task RemovePermissionOverwrite(IRole role) { await Discord.BaseClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index e15d7578a..877483645 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -37,14 +37,19 @@ namespace Discord.Rest Update(model); } - protected override async Task> GetUsers() + public override async Task GetUser(ulong id) + { + var user = await Guild.GetUser(id).ConfigureAwait(false); + if (user != null && PermissionUtilities.GetValue(PermissionHelper.Resolve(user, this), ChannelPermission.ReadMessages)) + return user; + return null; + } + public override async Task> GetUsers() { var users = await Guild.GetUsers().ConfigureAwait(false); return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.ReadMessages)); } - - /// - public Task GetMessage(ulong id) { throw new NotSupportedException(); } //Not implemented + /// public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { @@ -101,8 +106,10 @@ namespace Discord.Rest /// public override string ToString() => $"{base.ToString()} [Text]"; - async Task IMessageChannel.GetMessage(ulong id) - => await GetMessage(id).ConfigureAwait(false); + IEnumerable IMessageChannel.CachedMessages => Array.Empty(); + + Task IMessageChannel.GetCachedMessage(ulong id) + => Task.FromResult(null); async Task> IMessageChannel.GetMessages(int limit) => await GetMessages(limit).ConfigureAwait(false); async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index 703b58c01..8c76f3290 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -1,7 +1,6 @@ using Discord.API.Rest; using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; @@ -33,11 +32,8 @@ namespace Discord.Rest Update(model); } - protected override async Task> GetUsers() - { - var users = await Guild.GetUsers().ConfigureAwait(false); - return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.Connect)); - } + public override Task GetUser(ulong id) { throw new NotSupportedException(); } + public override Task> GetUsers() { throw new NotSupportedException(); } /// public override string ToString() => $"{base.ToString()} [Voice]"; diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index d68ff73c6..055c91bd4 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -134,7 +134,6 @@ namespace Discord.Rest var args = new ModifyGuildEmbedParams(); func(args); var model = await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); - Update(model); } /// diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index c27c06892..d36d094fb 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -109,7 +109,7 @@ namespace Discord.Rest IGuild IGuildUser.Guild => Guild; IReadOnlyList IGuildUser.Roles => Roles; - ulong? IGuildUser.VoiceChannelId => null; + IVoiceChannel IGuildUser.VoiceChannel => null; ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => GetPermissions(channel); diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index cb5294da6..84eb36916 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -24,6 +24,7 @@ namespace Discord.WebSocket public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); /// public IEnumerable Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + public IEnumerable CachedMessages => _messages.Messages; internal DMChannel(DiscordClient discord, Model model) { @@ -52,15 +53,20 @@ namespace Discord.WebSocket return null; } - /// + /// Gets the message from this channel's cache with the given id, or null if none was found. + public Message GetCachedMessage(ulong id) + { + return _messages.Get(id); + } + /// Gets the last N messages from this message channel. public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { - return await _messages.GetMany(null, Direction.Before, limit).ConfigureAwait(false); + return await _messages.Download(null, Direction.Before, limit).ConfigureAwait(false); } - /// + /// Gets a collection of messages in this channel. public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { - return await _messages.GetMany(fromMessageId, dir, limit).ConfigureAwait(false); + return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); } /// @@ -111,13 +117,14 @@ namespace Discord.WebSocket public override string ToString() => $"@{Recipient} [DM]"; IDMUser IDMChannel.Recipient => Recipient; + IEnumerable IMessageChannel.CachedMessages => CachedMessages; Task> IChannel.GetUsers() => Task.FromResult(Users); Task IChannel.GetUser(ulong id) => Task.FromResult(GetUser(id)); - Task IMessageChannel.GetMessage(ulong id) - => throw new NotSupportedException(); + Task IMessageChannel.GetCachedMessage(ulong id) + => Task.FromResult(GetCachedMessage(id)); async Task> IMessageChannel.GetMessages(int limit) => await GetMessages(limit).ConfigureAwait(false); async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 645eec5f3..c3a895b28 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket public abstract class GuildChannel : IGuildChannel { private ConcurrentDictionary _overwrites; - private ChannelPermissionsCache _permissions; + internal ChannelPermissionsCache _permissions; /// public ulong Id { get; } @@ -22,6 +22,7 @@ namespace Discord.WebSocket public string Name { get; private set; } /// public int Position { get; private set; } + public abstract IEnumerable Users { get; } /// public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); @@ -56,20 +57,13 @@ namespace Discord.WebSocket var args = new ModifyGuildChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } /// Gets a user in this channel with the given id. - public async Task GetUser(ulong id) - { - var model = await Discord.BaseClient.GetGuildMember(Guild.Id, id).ConfigureAwait(false); - if (model != null) - return new GuildUser(Guild, model); - return null; - } - protected abstract Task> GetUsers(); + public abstract GuildUser GetUser(ulong id); - /// Gets the permission overwrite for a specific user, or null if one does not exist. + /// public OverwritePermissions? GetPermissionOverwrite(IUser user) { Overwrite value; @@ -77,7 +71,7 @@ namespace Discord.WebSocket return value.Permissions; return null; } - /// Gets the permission overwrite for a specific role, or null if one does not exist. + /// public OverwritePermissions? GetPermissionOverwrite(IRole role) { Overwrite value; @@ -92,35 +86,27 @@ namespace Discord.WebSocket return models.Select(x => new GuildInvite(Guild, x)); } - /// Adds or updates the permission overwrite for the given user. + /// public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; await Discord.BaseClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); - _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); } - /// Adds or updates the permission overwrite for the given role. + /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; await Discord.BaseClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); - _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); } - /// Removes the permission overwrite for the given user, if one exists. + /// public async Task RemovePermissionOverwrite(IUser user) { await Discord.BaseClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); - - Overwrite value; - _overwrites.TryRemove(user.Id, out value); } - /// Removes the permission overwrite for the given role, if one exists. + /// public async Task RemovePermissionOverwrite(IRole role) { await Discord.BaseClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); - - Overwrite value; - _overwrites.TryRemove(role.Id, out value); } /// Creates a new invite to this channel. @@ -152,14 +138,14 @@ namespace Discord.WebSocket => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); async Task> IGuildChannel.GetInvites() => await GetInvites().ConfigureAwait(false); - async Task> IGuildChannel.GetUsers() - => await GetUsers().ConfigureAwait(false); - async Task IGuildChannel.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); - async Task> IChannel.GetUsers() - => await GetUsers().ConfigureAwait(false); - async Task IChannel.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); + Task> IGuildChannel.GetUsers() + => Task.FromResult>(Users); + Task IGuildChannel.GetUser(ulong id) + => Task.FromResult(GetUser(id)); + Task> IChannel.GetUsers() + => Task.FromResult>(Users); + Task IChannel.GetUser(ulong id) + => Task.FromResult(GetUser(id)); Task IUpdateable.Update() => Task.CompletedTask; } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index 23b3c2f70..0d8199bb7 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -1,6 +1,7 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -17,6 +18,9 @@ namespace Discord.WebSocket /// public string Mention => MentionHelper.Mention(this); + public override IEnumerable Users + => _permissions.Members.Where(x => x.Permissions.ReadMessages).Select(x => x.User).ToImmutableArray(); + public IEnumerable CachedMessages => _messages.Messages; internal TextChannel(Guild guild, Model model) : base(guild, model) @@ -39,27 +43,28 @@ namespace Discord.WebSocket await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } - protected override async Task> GetUsers() + /// Gets the message from this channel's cache with the given id, or null if none was found. + public Message GetCachedMessage(ulong id) { - var users = await Guild.GetUsers().ConfigureAwait(false); - return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.ReadMessages)); + return _messages.Get(id); } - - /// - public Task GetMessage(ulong id) { throw new NotSupportedException(); } //Not implemented - /// + /// Gets the last N messages from this message channel. public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { - var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); - return models.Select(x => new Message(this, x)); + return await _messages.Download(null, Direction.Before, limit).ConfigureAwait(false); } - /// + /// Gets a collection of messages in this channel. public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { - var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); - return models.Select(x => new Message(this, x)); + return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false); + } + + public override GuildUser GetUser(ulong id) + { + var member = _permissions.Get(id); + if (member != null && member.Value.Permissions.ReadMessages) + return member.Value.User; + return null; } /// @@ -103,8 +108,10 @@ namespace Discord.WebSocket /// public override string ToString() => $"{base.ToString()} [Text]"; - async Task IMessageChannel.GetMessage(ulong id) - => await GetMessage(id).ConfigureAwait(false); + IEnumerable IMessageChannel.CachedMessages => CachedMessages; + + Task IMessageChannel.GetCachedMessage(ulong id) + => Task.FromResult(GetCachedMessage(id)); async Task> IMessageChannel.GetMessages(int limit) => await GetMessages(limit).ConfigureAwait(false); async Task> IMessageChannel.GetMessages(ulong fromMessageId, Direction dir, int limit) diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index 286a67520..40c7ee586 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -12,6 +12,9 @@ namespace Discord.WebSocket /// public int Bitrate { get; private set; } + public override IEnumerable Users + => Guild.Users.Where(x => x.VoiceChannel == this); + internal VoiceChannel(Guild guild, Model model) : base(guild, model) { @@ -32,10 +35,12 @@ namespace Discord.WebSocket await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } - protected override async Task> GetUsers() + public override GuildUser GetUser(ulong id) { - var users = await Guild.GetUsers().ConfigureAwait(false); - return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.Connect)); + var member = _permissions.Get(id); + if (member != null && member.Value.Permissions.ReadMessages) + return member.Value.User; + return null; } /// diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 95c4a1413..3f68395d6 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -56,6 +56,7 @@ namespace Discord.WebSocket public Role EveryoneRole => GetRole(Id); /// Gets a collection of all roles in this guild. public IEnumerable Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty(); + public IEnumerable Users => Array.Empty(); internal Guild(DiscordClient discord, Model model) { diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 614476b5b..919d27fe1 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -22,6 +22,8 @@ namespace Discord.WebSocket public DateTime JoinedAt { get; private set; } /// public string Nickname { get; private set; } + /// + public VoiceChannel VoiceChannel { get; private set; } /// public IReadOnlyList Roles => _roles; @@ -103,7 +105,7 @@ namespace Discord.WebSocket IGuild IGuildUser.Guild => Guild; IReadOnlyList IGuildUser.Roles => Roles; - ulong? IGuildUser.VoiceChannelId => null; + IVoiceChannel IGuildUser.VoiceChannel => VoiceChannel; ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => GetPermissions(channel); diff --git a/src/Discord.Net/WebSocket/MessageCache.cs b/src/Discord.Net/WebSocket/MessageCache.cs index f3ef2b58b..1552dd2b1 100644 --- a/src/Discord.Net/WebSocket/MessageCache.cs +++ b/src/Discord.Net/WebSocket/MessageCache.cs @@ -16,6 +16,8 @@ namespace Discord.WebSocket private readonly ConcurrentQueue _orderedMessages; private readonly int _size; + public IEnumerable Messages => _messages.Select(x => x.Value); + public MessageCache(DiscordClient discord, IMessageChannel channel) { _discord = discord; @@ -51,13 +53,11 @@ namespace Discord.WebSocket return result; return null; } - public async Task> GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + public IImmutableList GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { - //TODO: Test heavily - if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); if (limit == 0) return ImmutableArray.Empty; - + IEnumerable cachedMessageIds; if (fromMessageId == null) cachedMessageIds = _orderedMessages; @@ -66,7 +66,7 @@ namespace Discord.WebSocket else cachedMessageIds = _orderedMessages.Where(x => x > fromMessageId.Value); - var cachedMessages = cachedMessageIds + return cachedMessageIds .Take(limit) .Select(x => { @@ -76,19 +76,28 @@ namespace Discord.WebSocket return null; }) .Where(x => x != null) - .ToArray(); + .ToImmutableArray(); + } + + public async Task> Download(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + { + //TODO: Test heavily + + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); + if (limit == 0) return ImmutableArray.Empty; - if (cachedMessages.Length == limit) + var cachedMessages = GetMany(fromMessageId, dir, limit); + if (cachedMessages.Count == limit) return cachedMessages; - else if (cachedMessages.Length > limit) - return cachedMessages.Skip(cachedMessages.Length - limit); + else if (cachedMessages.Count > limit) + return cachedMessages.Skip(cachedMessages.Count - limit); else { var args = new GetChannelMessagesParams { - Limit = limit - cachedMessages.Length, + Limit = limit - cachedMessages.Count, RelativeDirection = dir, - RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Length - 1].Id + RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id }; var downloadedMessages = await _discord.BaseClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, x))).ToImmutableArray(); From fe042e51bcc65a6fdb15e176f81f3753a7e01684 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 23:01:01 -0300 Subject: [PATCH 05/39] Cleaned up Invites --- .../Common/Entities/Channels/IGuildChannel.cs | 4 +- .../Common/Entities/Guilds/IGuild.cs | 4 +- .../{IGuildInvite.cs => IInviteMetadata.cs} | 5 +- .../Common/Entities/Invites/IPublicInvite.cs | 10 ---- .../Common/Entities/Invites/Invite.cs | 36 +++++++++---- .../Common/Entities/Invites/InviteMetadata.cs | 32 ++++++++++++ .../Common/Entities/Invites/PublicInvite.cs | 39 -------------- src/Discord.Net/Discord.Net.csproj | 7 +-- src/Discord.Net/IDiscordClient.cs | 2 +- src/Discord.Net/Rest/DiscordClient.cs | 6 +-- .../Rest/Entities/Channels/GuildChannel.cs | 12 ++--- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 12 ++--- .../Rest/Entities/Invites/GuildInvite.cs | 52 ------------------- src/Discord.Net/WebSocket/DiscordClient.cs | 2 +- .../Entities/Channels/GuildChannel.cs | 12 ++--- .../WebSocket/Entities/Guilds/Guild.cs | 12 ++--- .../WebSocket/Entities/Invites/GuildInvite.cs | 52 ------------------- 17 files changed, 93 insertions(+), 206 deletions(-) rename src/Discord.Net/Common/Entities/Invites/{IGuildInvite.cs => IInviteMetadata.cs} (81%) delete mode 100644 src/Discord.Net/Common/Entities/Invites/IPublicInvite.cs create mode 100644 src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs delete mode 100644 src/Discord.Net/Common/Entities/Invites/PublicInvite.cs delete mode 100644 src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs b/src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs index 456f6931e..0a6cf2f1b 100644 --- a/src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs @@ -20,9 +20,9 @@ namespace Discord /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// If true, a user accepting this invite will be kicked from the guild after closing their client. /// If true, creates a human-readable link. Not supported if maxAge is set to null. - Task CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false); + Task CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false); /// Returns a collection of all invites to this channel. - Task> GetInvites(); + Task> GetInvites(); /// Gets a collection of permission overwrites for this channel. IReadOnlyDictionary PermissionOverwrites { get; } diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuild.cs b/src/Discord.Net/Common/Entities/Guilds/IGuild.cs index 9d6518612..2fc618196 100644 --- a/src/Discord.Net/Common/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net/Common/Entities/Guilds/IGuild.cs @@ -70,13 +70,13 @@ namespace Discord Task CreateVoiceChannel(string name); /// Gets a collection of all invites to this guild. - Task> GetInvites(); + Task> GetInvites(); /// Creates a new invite to this guild. /// The time (in seconds) until the invite expires. Set to null to never expire. /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// If true, a user accepting this invite will be kicked from the guild after closing their client. /// If true, creates a human-readable link. Not supported if maxAge is set to null. - Task CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false); + Task CreateInvite(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool withXkcd = false); /// Gets a collection of all roles in this guild. Task> GetRoles(); diff --git a/src/Discord.Net/Common/Entities/Invites/IGuildInvite.cs b/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs similarity index 81% rename from src/Discord.Net/Common/Entities/Invites/IGuildInvite.cs rename to src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs index 25e6d0e80..9e68b8a82 100644 --- a/src/Discord.Net/Common/Entities/Invites/IGuildInvite.cs +++ b/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs @@ -1,6 +1,6 @@ namespace Discord { - public interface IGuildInvite : IDeletable, IInvite + public interface IInviteMetadata : IDeletable, IInvite { /// Returns true if this invite was revoked. bool IsRevoked { get; } @@ -12,8 +12,5 @@ int? MaxUses { get; } /// Gets the amount of times this invite has been used. int Uses { get; } - - /// Gets the guild this invite is linked to. - IGuild Guild { get; } } } \ No newline at end of file diff --git a/src/Discord.Net/Common/Entities/Invites/IPublicInvite.cs b/src/Discord.Net/Common/Entities/Invites/IPublicInvite.cs deleted file mode 100644 index 1d518bd0a..000000000 --- a/src/Discord.Net/Common/Entities/Invites/IPublicInvite.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Discord -{ - public interface IPublicInvite : IInvite - { - /// Gets the name of the the channel this invite is linked to. - string ChannelName { get; } - /// Gets the name of the guild this invite is linked to. - string GuildName { get; } - } -} \ No newline at end of file diff --git a/src/Discord.Net/Common/Entities/Invites/Invite.cs b/src/Discord.Net/Common/Entities/Invites/Invite.cs index 9ea98fd18..feae2119e 100644 --- a/src/Discord.Net/Common/Entities/Invites/Invite.cs +++ b/src/Discord.Net/Common/Entities/Invites/Invite.cs @@ -3,33 +3,43 @@ using Model = Discord.API.Invite; namespace Discord { - public abstract class Invite : IInvite + public class Invite : IInvite { - protected ulong _guildId, _channelId; - /// public string Code { get; } + internal IDiscordClient Discord { get; } + + /// + public ulong GuildId { get; private set; } + /// + public ulong ChannelId { get; private set; } + /// + public string XkcdCode { get; private set; } + /// + public string GuildName { get; private set; } /// - public string XkcdCode { get; } + public string ChannelName { get; private set; } /// public string Url => $"{DiscordConfig.InviteUrl}/{XkcdCode ?? Code}"; /// public string XkcdUrl => XkcdCode != null ? $"{DiscordConfig.InviteUrl}/{XkcdCode}" : null; - internal abstract IDiscordClient Discord { get; } - internal Invite(Model model) + internal Invite(IDiscordClient discord, Model model) { + Discord = discord; Code = model.Code; - XkcdCode = model.XkcdPass; Update(model); } protected virtual void Update(Model model) { - _guildId = model.Guild.Id; - _channelId = model.Channel.Id; + XkcdCode = model.XkcdPass; + GuildId = model.Guild.Id; + ChannelId = model.Channel.Id; + GuildName = model.Guild.Name; + ChannelName = model.Channel.Name; } /// @@ -38,11 +48,15 @@ namespace Discord await Discord.BaseClient.AcceptInvite(Code).ConfigureAwait(false); } + /// + public async Task Delete() + { + await Discord.BaseClient.DeleteInvite(Code).ConfigureAwait(false); + } + /// public override string ToString() => XkcdUrl ?? Url; string IEntity.Id => Code; - ulong IInvite.GuildId => _guildId; - ulong IInvite.ChannelId => _channelId; } } diff --git a/src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs b/src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs new file mode 100644 index 000000000..61f353ebd --- /dev/null +++ b/src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs @@ -0,0 +1,32 @@ +using Model = Discord.API.InviteMetadata; + +namespace Discord +{ + public class InviteMetadata : Invite, IInviteMetadata + { + /// + public bool IsRevoked { get; private set; } + /// + public bool IsTemporary { get; private set; } + /// + public int? MaxAge { get; private set; } + /// + public int? MaxUses { get; private set; } + /// + public int Uses { get; private set; } + + internal InviteMetadata(IDiscordClient client, Model model) + : base(client, model) + { + Update(model); + } + private void Update(Model model) + { + IsRevoked = model.Revoked; + IsTemporary = model.Temporary; + MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; + MaxUses = model.MaxUses; + Uses = model.Uses; + } + } +} diff --git a/src/Discord.Net/Common/Entities/Invites/PublicInvite.cs b/src/Discord.Net/Common/Entities/Invites/PublicInvite.cs deleted file mode 100644 index 3a2f42394..000000000 --- a/src/Discord.Net/Common/Entities/Invites/PublicInvite.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Model = Discord.API.Invite; - -namespace Discord.Rest -{ - public class PublicInvite : Invite, IPublicInvite - { - /// - public string GuildName { get; private set; } - /// - public string ChannelName { get; private set; } - - /// - public ulong GuildId => _guildId; - /// - public ulong ChannelId => _channelId; - - internal override IDiscordClient Discord { get; } - - internal PublicInvite(IDiscordClient discord, Model model) - : base(model) - { - Discord = discord; - } - protected override void Update(Model model) - { - base.Update(model); - GuildName = model.Guild.Name; - ChannelName = model.Channel.Name; - } - - /// - public async Task Update() - { - var model = await Discord.BaseClient.GetInvite(Code).ConfigureAwait(false); - Update(model); - } - } -} diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 0c5742f1c..af5ce3e12 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -118,7 +118,6 @@ - @@ -139,7 +138,7 @@ - + @@ -168,9 +167,7 @@ - - @@ -217,7 +214,7 @@ - + diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs index 4d12632cd..068f56bff 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net/IDiscordClient.cs @@ -27,7 +27,7 @@ namespace Discord Task> GetGuilds(); Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null); - Task GetInvite(string inviteIdOrXkcd); + Task GetInvite(string inviteIdOrXkcd); Task GetUser(ulong id); Task GetUser(string username, ushort discriminator); diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index 504ff86f9..f57f9694c 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -168,11 +168,11 @@ namespace Discord.Rest return models.Select(x => new DMChannel(this, x)); } - public async Task GetInvite(string inviteIdOrXkcd) + public async Task GetInvite(string inviteIdOrXkcd) { var model = await BaseClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); if (model != null) - return new PublicInvite(this, model); + return new Invite(this, model); return null; } @@ -269,7 +269,7 @@ namespace Discord.Rest => await GetDMChannels().ConfigureAwait(false); async Task> IDiscordClient.GetConnections() => await GetConnections().ConfigureAwait(false); - async Task IDiscordClient.GetInvite(string inviteIdOrXkcd) + async Task IDiscordClient.GetInvite(string inviteIdOrXkcd) => await GetInvite(inviteIdOrXkcd).ConfigureAwait(false); async Task IDiscordClient.GetGuild(ulong id) => await GetGuild(id).ConfigureAwait(false); diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index c06cb8e70..635709948 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -81,10 +81,10 @@ namespace Discord.Rest return null; } /// Downloads a collection of all invites to this channel. - public async Task> GetInvites() + public async Task> GetInvites() { var models = await Discord.BaseClient.GetChannelInvites(Id).ConfigureAwait(false); - return models.Select(x => new GuildInvite(Guild, x)); + return models.Select(x => new InviteMetadata(Discord, x)); } /// @@ -123,7 +123,7 @@ namespace Discord.Rest /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// If true, a user accepting this invite will be kicked from the guild after closing their client. /// If true, creates a human-readable link. Not supported if maxAge is set to null. - public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) { var args = new CreateChannelInviteParams { @@ -133,7 +133,7 @@ namespace Discord.Rest XkcdPass = withXkcd }; var model = await Discord.BaseClient.CreateChannelInvite(Id, args).ConfigureAwait(false); - return new GuildInvite(Guild, model); + return new InviteMetadata(Discord, model); } /// @@ -149,9 +149,9 @@ namespace Discord.Rest } IGuild IGuildChannel.Guild => Guild; - async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); - async Task> IGuildChannel.GetInvites() + async Task> IGuildChannel.GetInvites() => await GetInvites().ConfigureAwait(false); async Task> IGuildChannel.GetUsers() => await GetUsers().ConfigureAwait(false); diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index 055c91bd4..c1ee23236 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -231,13 +231,13 @@ namespace Discord.Rest } /// Gets a collection of all invites to this guild. - public async Task> GetInvites() + public async Task> GetInvites() { var models = await Discord.BaseClient.GetGuildInvites(Id).ConfigureAwait(false); - return models.Select(x => new GuildInvite(this, x)); + return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. - public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) { if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); @@ -250,7 +250,7 @@ namespace Discord.Rest XkcdPass = withXkcd }; var model = await Discord.BaseClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); - return new GuildInvite(this, model); + return new InviteMetadata(Discord, model); } /// Gets the role in this guild with the provided id, or null if not found. @@ -344,7 +344,7 @@ namespace Discord.Rest => await GetChannel(id).ConfigureAwait(false); async Task> IGuild.GetChannels() => await GetChannels().ConfigureAwait(false); - async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); async Task IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) => await CreateRole(name, permissions, color, isHoisted).ConfigureAwait(false); @@ -352,7 +352,7 @@ namespace Discord.Rest => await CreateTextChannel(name).ConfigureAwait(false); async Task IGuild.CreateVoiceChannel(string name) => await CreateVoiceChannel(name).ConfigureAwait(false); - async Task> IGuild.GetInvites() + async Task> IGuild.GetInvites() => await GetInvites().ConfigureAwait(false); Task IGuild.GetRole(ulong id) => Task.FromResult(GetRole(id)); diff --git a/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs b/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs deleted file mode 100644 index 71b0541e2..000000000 --- a/src/Discord.Net/Rest/Entities/Invites/GuildInvite.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Threading.Tasks; -using Model = Discord.API.InviteMetadata; - -namespace Discord.Rest -{ - public class GuildInvite : Invite, IGuildInvite - { - /// Gets the guild this invite is linked to. - public Guild Guild { get; private set; } - /// - public ulong ChannelId { get; private set; } - - /// - public bool IsRevoked { get; private set; } - /// - public bool IsTemporary { get; private set; } - /// - public int? MaxAge { get; private set; } - /// - public int? MaxUses { get; private set; } - /// - public int Uses { get; private set; } - - internal override IDiscordClient Discord => Guild.Discord; - - internal GuildInvite(Guild guild, Model model) - : base(model) - { - Guild = guild; - - Update(model); //Causes base.Update(Model) to be run twice, but that's fine. - } - private void Update(Model model) - { - base.Update(model); - IsRevoked = model.Revoked; - IsTemporary = model.Temporary; - MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; - MaxUses = model.MaxUses; - Uses = model.Uses; - } - - /// - public async Task Delete() - { - await Discord.BaseClient.DeleteInvite(Code).ConfigureAwait(false); - } - - IGuild IGuildInvite.Guild => Guild; - ulong IInvite.GuildId => Guild.Id; - } -} diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 4032a5539..0af5cceb9 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -86,7 +86,7 @@ namespace Discord.WebSocket throw new NotImplementedException(); } - public Task GetInvite(string inviteIdOrXkcd) + public Task GetInvite(string inviteIdOrXkcd) { throw new NotImplementedException(); } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index c3a895b28..7f33e1eae 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -80,10 +80,10 @@ namespace Discord.WebSocket return null; } /// Downloads a collection of all invites to this channel. - public async Task> GetInvites() + public async Task> GetInvites() { var models = await Discord.BaseClient.GetChannelInvites(Id).ConfigureAwait(false); - return models.Select(x => new GuildInvite(Guild, x)); + return models.Select(x => new InviteMetadata(Discord, x)); } /// @@ -114,7 +114,7 @@ namespace Discord.WebSocket /// The max amount of times this invite may be used. Set to null to have unlimited uses. /// If true, a user accepting this invite will be kicked from the guild after closing their client. /// If true, creates a human-readable link. Not supported if maxAge is set to null. - public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) { var args = new CreateChannelInviteParams { @@ -124,7 +124,7 @@ namespace Discord.WebSocket XkcdPass = withXkcd }; var model = await Discord.BaseClient.CreateChannelInvite(Id, args).ConfigureAwait(false); - return new GuildInvite(Guild, model); + return new InviteMetadata(Discord, model); } /// @@ -134,9 +134,9 @@ namespace Discord.WebSocket } IGuild IGuildChannel.Guild => Guild; - async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); - async Task> IGuildChannel.GetInvites() + async Task> IGuildChannel.GetInvites() => await GetInvites().ConfigureAwait(false); Task> IGuildChannel.GetUsers() => Task.FromResult>(Users); diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 3f68395d6..fa5bba1c0 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -224,13 +224,13 @@ namespace Discord.WebSocket } /// Gets a collection of all invites to this guild. - public async Task> GetInvites() + public async Task> GetInvites() { var models = await Discord.BaseClient.GetGuildInvites(Id).ConfigureAwait(false); - return models.Select(x => new GuildInvite(this, x)); + return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. - public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool isTemporary = false, bool withXkcd = false) { if (maxAge <= 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); if (maxUses <= 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); @@ -243,7 +243,7 @@ namespace Discord.WebSocket XkcdPass = withXkcd }; var model = await Discord.BaseClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); - return new GuildInvite(this, model); + return new InviteMetadata(Discord, model); } /// Gets the role in this guild with the provided id, or null if not found. @@ -337,7 +337,7 @@ namespace Discord.WebSocket => await GetChannel(id).ConfigureAwait(false); async Task> IGuild.GetChannels() => await GetChannels().ConfigureAwait(false); - async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) + async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); async Task IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) => await CreateRole(name, permissions, color, isHoisted).ConfigureAwait(false); @@ -345,7 +345,7 @@ namespace Discord.WebSocket => await CreateTextChannel(name).ConfigureAwait(false); async Task IGuild.CreateVoiceChannel(string name) => await CreateVoiceChannel(name).ConfigureAwait(false); - async Task> IGuild.GetInvites() + async Task> IGuild.GetInvites() => await GetInvites().ConfigureAwait(false); Task IGuild.GetRole(ulong id) => Task.FromResult(GetRole(id)); diff --git a/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs b/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs deleted file mode 100644 index c78c7a9e9..000000000 --- a/src/Discord.Net/WebSocket/Entities/Invites/GuildInvite.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Threading.Tasks; -using Model = Discord.API.InviteMetadata; - -namespace Discord.WebSocket -{ - public class GuildInvite : Invite, IGuildInvite - { - /// Gets the guild this invite is linked to. - public Guild Guild { get; private set; } - /// - public ulong ChannelId { get; private set; } - - /// - public bool IsRevoked { get; private set; } - /// - public bool IsTemporary { get; private set; } - /// - public int? MaxAge { get; private set; } - /// - public int? MaxUses { get; private set; } - /// - public int Uses { get; private set; } - - internal override IDiscordClient Discord => Guild.Discord; - - internal GuildInvite(Guild guild, Model model) - : base(model) - { - Guild = guild; - - Update(model); //Causes base.Update(Model) to be run twice, but that's fine. - } - private void Update(Model model) - { - base.Update(model); - IsRevoked = model.Revoked; - IsTemporary = model.Temporary; - MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; - MaxUses = model.MaxUses; - Uses = model.Uses; - } - - /// - public async Task Delete() - { - await Discord.BaseClient.DeleteInvite(Code).ConfigureAwait(false); - } - - IGuild IGuildInvite.Guild => Guild; - ulong IInvite.GuildId => Guild.Id; - } -} From 6615b852c1c27468b61f2448dde84e915b458310 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 14 May 2016 23:03:45 -0300 Subject: [PATCH 06/39] Moved IDeletable from InviteMetadata to Invite --- src/Discord.Net/Common/Entities/Invites/IInvite.cs | 2 +- src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Common/Entities/Invites/IInvite.cs b/src/Discord.Net/Common/Entities/Invites/IInvite.cs index d0eb8888c..4b0f55f59 100644 --- a/src/Discord.Net/Common/Entities/Invites/IInvite.cs +++ b/src/Discord.Net/Common/Entities/Invites/IInvite.cs @@ -2,7 +2,7 @@ namespace Discord { - public interface IInvite : IEntity + public interface IInvite : IEntity, IDeletable { /// Gets the unique identifier for this invite. string Code { get; } diff --git a/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs index 9e68b8a82..a2e18a2e7 100644 --- a/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs @@ -1,6 +1,6 @@ namespace Discord { - public interface IInviteMetadata : IDeletable, IInvite + public interface IInviteMetadata : IInvite { /// Returns true if this invite was revoked. bool IsRevoked { get; } From d1d6bf2c52b951d25b7f184f0b650228a04190a7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 01:16:03 -0300 Subject: [PATCH 07/39] Cleaned up helpers and permissions, bumped perms up to uint64. --- src/Discord.Net/API/Common/Overwrite.cs | 4 +- src/Discord.Net/API/Common/Role.cs | 2 +- .../Rest/ModifyChannelPermissionsParams.cs | 4 +- .../API/Rest/ModifyGuildRoleParams.cs | 2 +- .../DateTimeHelper.cs => DateTimeUtils.cs} | 2 +- .../Permissions/ChannelPermissions.cs | 80 ++++----- .../Entities/Permissions/GuildPermissions.cs | 104 ++++++------ .../Permissions/OverwritePermissions.cs | 80 ++++----- .../Permissions/PermissionUtilities.cs | 87 ---------- .../Entities/Permissions/Permissions.cs | 152 ++++++++++++++++++ .../Common/{Helpers => }/EventExtensions.cs | 0 .../Common/Helpers/PermissionHelper.cs | 64 -------- .../MentionHelper.cs => MentionUtils.cs} | 2 +- src/Discord.Net/Discord.Net.csproj | 9 +- .../Rest/Entities/Channels/DMChannel.cs | 2 +- .../Rest/Entities/Channels/GuildChannel.cs | 2 +- .../Rest/Entities/Channels/TextChannel.cs | 6 +- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 2 +- .../Rest/Entities/Guilds/GuildEmbed.cs | 2 +- .../Rest/Entities/Guilds/UserGuild.cs | 2 +- src/Discord.Net/Rest/Entities/Message.cs | 8 +- src/Discord.Net/Rest/Entities/Role.cs | 4 +- .../Rest/Entities/Users/GuildUser.cs | 13 +- src/Discord.Net/Rest/Entities/Users/User.cs | 6 +- .../WebSocket/ChannelPermissionsCache.cs | 6 +- .../WebSocket/Entities/Channels/DMChannel.cs | 2 +- .../Entities/Channels/GuildChannel.cs | 2 +- .../Entities/Channels/TextChannel.cs | 2 +- .../WebSocket/Entities/Guilds/Guild.cs | 2 +- src/Discord.Net/WebSocket/Entities/Message.cs | 8 +- src/Discord.Net/WebSocket/Entities/Role.cs | 4 +- .../WebSocket/Entities/Users/GuildUser.cs | 11 +- .../WebSocket/Entities/Users/User.cs | 6 +- 33 files changed, 346 insertions(+), 336 deletions(-) rename src/Discord.Net/Common/{Helpers/DateTimeHelper.cs => DateTimeUtils.cs} (93%) delete mode 100644 src/Discord.Net/Common/Entities/Permissions/PermissionUtilities.cs create mode 100644 src/Discord.Net/Common/Entities/Permissions/Permissions.cs rename src/Discord.Net/Common/{Helpers => }/EventExtensions.cs (100%) delete mode 100644 src/Discord.Net/Common/Helpers/PermissionHelper.cs rename src/Discord.Net/Common/{Helpers/MentionHelper.cs => MentionUtils.cs} (99%) diff --git a/src/Discord.Net/API/Common/Overwrite.cs b/src/Discord.Net/API/Common/Overwrite.cs index f1da83b9e..ab9799f71 100644 --- a/src/Discord.Net/API/Common/Overwrite.cs +++ b/src/Discord.Net/API/Common/Overwrite.cs @@ -9,8 +9,8 @@ namespace Discord.API [JsonProperty("type")] public PermissionTarget TargetType { get; set; } [JsonProperty("deny")] - public uint Deny { get; set; } + public ulong Deny { get; set; } [JsonProperty("allow")] - public uint Allow { get; set; } + public ulong Allow { get; set; } } } diff --git a/src/Discord.Net/API/Common/Role.cs b/src/Discord.Net/API/Common/Role.cs index bd447fcd1..55f1933b7 100644 --- a/src/Discord.Net/API/Common/Role.cs +++ b/src/Discord.Net/API/Common/Role.cs @@ -15,7 +15,7 @@ namespace Discord.API [JsonProperty("position")] public int? Position { get; set; } [JsonProperty("permissions")] - public uint? Permissions { get; set; } + public ulong? Permissions { get; set; } [JsonProperty("managed")] public bool? Managed { get; set; } } diff --git a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs index f102cccca..31a41086a 100644 --- a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs @@ -5,8 +5,8 @@ namespace Discord.API.Rest public class ModifyChannelPermissionsParams { [JsonProperty("allow")] - public Optional Allow { get; set; } + public Optional Allow { get; set; } [JsonProperty("deny")] - public Optional Deny { get; set; } + public Optional Deny { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs index 58a715ae9..d3b6979ec 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs @@ -7,7 +7,7 @@ namespace Discord.API.Rest [JsonProperty("name")] public Optional Name { get; set; } [JsonProperty("permissions")] - public Optional Permissions { get; set; } + public Optional Permissions { get; set; } [JsonProperty("position")] public Optional Position { get; set; } [JsonProperty("color")] diff --git a/src/Discord.Net/Common/Helpers/DateTimeHelper.cs b/src/Discord.Net/Common/DateTimeUtils.cs similarity index 93% rename from src/Discord.Net/Common/Helpers/DateTimeHelper.cs rename to src/Discord.Net/Common/DateTimeUtils.cs index a93efcb1d..57db38134 100644 --- a/src/Discord.Net/Common/Helpers/DateTimeHelper.cs +++ b/src/Discord.Net/Common/DateTimeUtils.cs @@ -2,7 +2,7 @@ namespace Discord { - internal static class DateTimeHelper + internal static class DateTimeUtils { private const ulong EpochTicks = 621355968000000000UL; diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs index 4c0710f82..c7a430e4a 100644 --- a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs @@ -25,74 +25,74 @@ namespace Discord } /// Gets a packed value representing all the permissions in this ChannelPermissions. - public uint RawValue { get; } + public ulong RawValue { get; } /// If True, a user may create invites. - public bool CreateInstantInvite => PermissionUtilities.GetValue(RawValue, ChannelPermission.CreateInstantInvite); + public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); /// If True, a user may create, delete and modify this channel. - public bool ManageChannel => PermissionUtilities.GetValue(RawValue, ChannelPermission.ManageChannel); + public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel); /// If True, a user may join channels. - public bool ReadMessages => PermissionUtilities.GetValue(RawValue, ChannelPermission.ReadMessages); + public bool ReadMessages => Permissions.GetValue(RawValue, ChannelPermission.ReadMessages); /// If True, a user may send messages. - public bool SendMessages => PermissionUtilities.GetValue(RawValue, ChannelPermission.SendMessages); + public bool SendMessages => Permissions.GetValue(RawValue, ChannelPermission.SendMessages); /// If True, a user may send text-to-speech messages. - public bool SendTTSMessages => PermissionUtilities.GetValue(RawValue, ChannelPermission.SendTTSMessages); + public bool SendTTSMessages => Permissions.GetValue(RawValue, ChannelPermission.SendTTSMessages); /// If True, a user may delete messages. - public bool ManageMessages => PermissionUtilities.GetValue(RawValue, ChannelPermission.ManageMessages); + public bool ManageMessages => Permissions.GetValue(RawValue, ChannelPermission.ManageMessages); /// If True, Discord will auto-embed links sent by this user. - public bool EmbedLinks => PermissionUtilities.GetValue(RawValue, ChannelPermission.EmbedLinks); + public bool EmbedLinks => Permissions.GetValue(RawValue, ChannelPermission.EmbedLinks); /// If True, a user may send files. - public bool AttachFiles => PermissionUtilities.GetValue(RawValue, ChannelPermission.AttachFiles); + public bool AttachFiles => Permissions.GetValue(RawValue, ChannelPermission.AttachFiles); /// If True, a user may read previous messages. - public bool ReadMessageHistory => PermissionUtilities.GetValue(RawValue, ChannelPermission.ReadMessageHistory); + public bool ReadMessageHistory => Permissions.GetValue(RawValue, ChannelPermission.ReadMessageHistory); /// If True, a user may mention @everyone. - public bool MentionEveryone => PermissionUtilities.GetValue(RawValue, ChannelPermission.MentionEveryone); + public bool MentionEveryone => Permissions.GetValue(RawValue, ChannelPermission.MentionEveryone); /// If True, a user may connect to a voice channel. - public bool Connect => PermissionUtilities.GetValue(RawValue, ChannelPermission.Connect); + public bool Connect => Permissions.GetValue(RawValue, ChannelPermission.Connect); /// If True, a user may speak in a voice channel. - public bool Speak => PermissionUtilities.GetValue(RawValue, ChannelPermission.Speak); + public bool Speak => Permissions.GetValue(RawValue, ChannelPermission.Speak); /// If True, a user may mute users. - public bool MuteMembers => PermissionUtilities.GetValue(RawValue, ChannelPermission.MuteMembers); + public bool MuteMembers => Permissions.GetValue(RawValue, ChannelPermission.MuteMembers); /// If True, a user may deafen users. - public bool DeafenMembers => PermissionUtilities.GetValue(RawValue, ChannelPermission.DeafenMembers); + public bool DeafenMembers => Permissions.GetValue(RawValue, ChannelPermission.DeafenMembers); /// If True, a user may move other users between voice channels. - public bool MoveMembers => PermissionUtilities.GetValue(RawValue, ChannelPermission.MoveMembers); + public bool MoveMembers => Permissions.GetValue(RawValue, ChannelPermission.MoveMembers); /// If True, a user may use voice-activity-detection rather than push-to-talk. - public bool UseVAD => PermissionUtilities.GetValue(RawValue, ChannelPermission.UseVAD); + public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); /// If True, a user may adjust permissions. This also implictly grants all other permissions. - public bool ManagePermissions => PermissionUtilities.GetValue(RawValue, ChannelPermission.ManagePermissions); + public bool ManagePermissions => Permissions.GetValue(RawValue, ChannelPermission.ManagePermissions); /// Creates a new ChannelPermissions with the provided packed value. - public ChannelPermissions(uint rawValue) { RawValue = rawValue; } + public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } - private ChannelPermissions(uint initialValue, bool? createInstantInvite = null, bool? manageChannel = null, + private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) { - uint value = initialValue; + ulong value = initialValue; - PermissionUtilities.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); - PermissionUtilities.SetValue(ref value, manageChannel, ChannelPermission.ManageChannel); - PermissionUtilities.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); - PermissionUtilities.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); - PermissionUtilities.SetValue(ref value, sendTTSMessages, ChannelPermission.SendTTSMessages); - PermissionUtilities.SetValue(ref value, manageMessages, ChannelPermission.ManageMessages); - PermissionUtilities.SetValue(ref value, embedLinks, ChannelPermission.EmbedLinks); - PermissionUtilities.SetValue(ref value, attachFiles, ChannelPermission.AttachFiles); - PermissionUtilities.SetValue(ref value, readMessageHistory, ChannelPermission.ReadMessageHistory); - PermissionUtilities.SetValue(ref value, mentionEveryone, ChannelPermission.MentionEveryone); - PermissionUtilities.SetValue(ref value, connect, ChannelPermission.Connect); - PermissionUtilities.SetValue(ref value, speak, ChannelPermission.Speak); - PermissionUtilities.SetValue(ref value, muteMembers, ChannelPermission.MuteMembers); - PermissionUtilities.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); - PermissionUtilities.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); - PermissionUtilities.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); - PermissionUtilities.SetValue(ref value, managePermissions, ChannelPermission.ManagePermissions); + Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); + Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannel); + Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); + Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); + Permissions.SetValue(ref value, sendTTSMessages, ChannelPermission.SendTTSMessages); + Permissions.SetValue(ref value, manageMessages, ChannelPermission.ManageMessages); + Permissions.SetValue(ref value, embedLinks, ChannelPermission.EmbedLinks); + Permissions.SetValue(ref value, attachFiles, ChannelPermission.AttachFiles); + Permissions.SetValue(ref value, readMessageHistory, ChannelPermission.ReadMessageHistory); + Permissions.SetValue(ref value, mentionEveryone, ChannelPermission.MentionEveryone); + Permissions.SetValue(ref value, connect, ChannelPermission.Connect); + Permissions.SetValue(ref value, speak, ChannelPermission.Speak); + Permissions.SetValue(ref value, muteMembers, ChannelPermission.MuteMembers); + Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); + Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); + Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); + Permissions.SetValue(ref value, managePermissions, ChannelPermission.ManagePermissions); RawValue = value; } @@ -121,8 +121,8 @@ namespace Discord public override string ToString() { var perms = new List(); - int x = 1; - for (byte i = 0; i < 32; i++, x <<= 1) + ulong x = 1; + for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((RawValue & x) != 0) { diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs index 55b62599e..2ab0e6ddf 100644 --- a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs @@ -10,62 +10,62 @@ namespace Discord public static readonly GuildPermissions All = new GuildPermissions(0b000111_111111_0011111111_0000111111); /// Gets a packed value representing all the permissions in this GuildPermissions. - public uint RawValue { get; } + public ulong RawValue { get; } /// If True, a user may create invites. - public bool CreateInstantInvite => PermissionUtilities.GetValue(RawValue, GuildPermission.CreateInstantInvite); + public bool CreateInstantInvite => Permissions.GetValue(RawValue, GuildPermission.CreateInstantInvite); /// If True, a user may ban users from the guild. - public bool BanMembers => PermissionUtilities.GetValue(RawValue, GuildPermission.BanMembers); + public bool BanMembers => Permissions.GetValue(RawValue, GuildPermission.BanMembers); /// If True, a user may kick users from the guild. - public bool KickMembers => PermissionUtilities.GetValue(RawValue, GuildPermission.KickMembers); + public bool KickMembers => Permissions.GetValue(RawValue, GuildPermission.KickMembers); /// If True, a user is granted all permissions, and cannot have them revoked via channel permissions. - public bool Administrator => PermissionUtilities.GetValue(RawValue, GuildPermission.Administrator); + public bool Administrator => Permissions.GetValue(RawValue, GuildPermission.Administrator); /// If True, a user may create, delete and modify channels. - public bool ManageChannels => PermissionUtilities.GetValue(RawValue, GuildPermission.ManageChannels); + public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); /// If True, a user may adjust guild properties. - public bool ManageGuild => PermissionUtilities.GetValue(RawValue, GuildPermission.ManageGuild); + public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); /// If True, a user may join channels. - public bool ReadMessages => PermissionUtilities.GetValue(RawValue, GuildPermission.ReadMessages); + public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); /// If True, a user may send messages. - public bool SendMessages => PermissionUtilities.GetValue(RawValue, GuildPermission.SendMessages); + public bool SendMessages => Permissions.GetValue(RawValue, GuildPermission.SendMessages); /// If True, a user may send text-to-speech messages. - public bool SendTTSMessages => PermissionUtilities.GetValue(RawValue, GuildPermission.SendTTSMessages); + public bool SendTTSMessages => Permissions.GetValue(RawValue, GuildPermission.SendTTSMessages); /// If True, a user may delete messages. - public bool ManageMessages => PermissionUtilities.GetValue(RawValue, GuildPermission.ManageMessages); + public bool ManageMessages => Permissions.GetValue(RawValue, GuildPermission.ManageMessages); /// If True, Discord will auto-embed links sent by this user. - public bool EmbedLinks => PermissionUtilities.GetValue(RawValue, GuildPermission.EmbedLinks); + public bool EmbedLinks => Permissions.GetValue(RawValue, GuildPermission.EmbedLinks); /// If True, a user may send files. - public bool AttachFiles => PermissionUtilities.GetValue(RawValue, GuildPermission.AttachFiles); + public bool AttachFiles => Permissions.GetValue(RawValue, GuildPermission.AttachFiles); /// If True, a user may read previous messages. - public bool ReadMessageHistory => PermissionUtilities.GetValue(RawValue, GuildPermission.ReadMessageHistory); + public bool ReadMessageHistory => Permissions.GetValue(RawValue, GuildPermission.ReadMessageHistory); /// If True, a user may mention @everyone. - public bool MentionEveryone => PermissionUtilities.GetValue(RawValue, GuildPermission.MentionEveryone); + public bool MentionEveryone => Permissions.GetValue(RawValue, GuildPermission.MentionEveryone); /// If True, a user may connect to a voice channel. - public bool Connect => PermissionUtilities.GetValue(RawValue, GuildPermission.Connect); + public bool Connect => Permissions.GetValue(RawValue, GuildPermission.Connect); /// If True, a user may speak in a voice channel. - public bool Speak => PermissionUtilities.GetValue(RawValue, GuildPermission.Speak); + public bool Speak => Permissions.GetValue(RawValue, GuildPermission.Speak); /// If True, a user may mute users. - public bool MuteMembers => PermissionUtilities.GetValue(RawValue, GuildPermission.MuteMembers); + public bool MuteMembers => Permissions.GetValue(RawValue, GuildPermission.MuteMembers); /// If True, a user may deafen users. - public bool DeafenMembers => PermissionUtilities.GetValue(RawValue, GuildPermission.DeafenMembers); + public bool DeafenMembers => Permissions.GetValue(RawValue, GuildPermission.DeafenMembers); /// If True, a user may move other users between voice channels. - public bool MoveMembers => PermissionUtilities.GetValue(RawValue, GuildPermission.MoveMembers); + public bool MoveMembers => Permissions.GetValue(RawValue, GuildPermission.MoveMembers); /// If True, a user may use voice-activity-detection rather than push-to-talk. - public bool UseVAD => PermissionUtilities.GetValue(RawValue, GuildPermission.UseVAD); + public bool UseVAD => Permissions.GetValue(RawValue, GuildPermission.UseVAD); /// If True, a user may change their own nickname. - public bool ChangeNickname => PermissionUtilities.GetValue(RawValue, GuildPermission.ChangeNickname); + public bool ChangeNickname => Permissions.GetValue(RawValue, GuildPermission.ChangeNickname); /// If True, a user may change the nickname of other users. - public bool ManageNicknames => PermissionUtilities.GetValue(RawValue, GuildPermission.ManageNicknames); + public bool ManageNicknames => Permissions.GetValue(RawValue, GuildPermission.ManageNicknames); /// If True, a user may adjust roles. - public bool ManageRoles => PermissionUtilities.GetValue(RawValue, GuildPermission.ManageRoles); + public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); /// Creates a new GuildPermissions with the provided packed value. - public GuildPermissions(uint rawValue) { RawValue = rawValue; } + public GuildPermissions(ulong rawValue) { RawValue = rawValue; } - private GuildPermissions(uint initialValue, bool? createInstantInvite = null, bool? kickMembers = null, + private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, bool? banMembers = null, bool? administrator = null, bool? manageChannel = null, bool? manageGuild = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, @@ -73,31 +73,31 @@ namespace Discord bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null) { - uint value = initialValue; + ulong value = initialValue; - PermissionUtilities.SetValue(ref value, createInstantInvite, GuildPermission.CreateInstantInvite); - PermissionUtilities.SetValue(ref value, banMembers, GuildPermission.BanMembers); - PermissionUtilities.SetValue(ref value, kickMembers, GuildPermission.KickMembers); - PermissionUtilities.SetValue(ref value, administrator, GuildPermission.Administrator); - PermissionUtilities.SetValue(ref value, manageChannel, GuildPermission.ManageChannels); - PermissionUtilities.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); - PermissionUtilities.SetValue(ref value, readMessages, GuildPermission.ReadMessages); - PermissionUtilities.SetValue(ref value, sendMessages, GuildPermission.SendMessages); - PermissionUtilities.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); - PermissionUtilities.SetValue(ref value, manageMessages, GuildPermission.ManageMessages); - PermissionUtilities.SetValue(ref value, embedLinks, GuildPermission.EmbedLinks); - PermissionUtilities.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); - PermissionUtilities.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); - PermissionUtilities.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); - PermissionUtilities.SetValue(ref value, connect, GuildPermission.Connect); - PermissionUtilities.SetValue(ref value, speak, GuildPermission.Speak); - PermissionUtilities.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); - PermissionUtilities.SetValue(ref value, deafenMembers, GuildPermission.DeafenMembers); - PermissionUtilities.SetValue(ref value, moveMembers, GuildPermission.MoveMembers); - PermissionUtilities.SetValue(ref value, useVoiceActivation, GuildPermission.UseVAD); - PermissionUtilities.SetValue(ref value, changeNickname, GuildPermission.ChangeNickname); - PermissionUtilities.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); - PermissionUtilities.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); + Permissions.SetValue(ref value, createInstantInvite, GuildPermission.CreateInstantInvite); + Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); + Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); + Permissions.SetValue(ref value, administrator, GuildPermission.Administrator); + Permissions.SetValue(ref value, manageChannel, GuildPermission.ManageChannels); + Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); + Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); + Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); + Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); + Permissions.SetValue(ref value, manageMessages, GuildPermission.ManageMessages); + Permissions.SetValue(ref value, embedLinks, GuildPermission.EmbedLinks); + Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); + Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); + Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); + Permissions.SetValue(ref value, connect, GuildPermission.Connect); + Permissions.SetValue(ref value, speak, GuildPermission.Speak); + Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); + Permissions.SetValue(ref value, deafenMembers, GuildPermission.DeafenMembers); + Permissions.SetValue(ref value, moveMembers, GuildPermission.MoveMembers); + Permissions.SetValue(ref value, useVoiceActivation, GuildPermission.UseVAD); + Permissions.SetValue(ref value, changeNickname, GuildPermission.ChangeNickname); + Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); + Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); RawValue = value; } @@ -130,8 +130,8 @@ namespace Discord public override string ToString() { var perms = new List(); - int x = 1; - for (byte i = 0; i < 32; i++, x <<= 1) + ulong x = 1; + for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((RawValue & x) != 0) { diff --git a/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs index 735b17dcf..5bfa3785a 100644 --- a/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs @@ -15,77 +15,77 @@ namespace Discord => new OverwritePermissions(0, ChannelPermissions.All(channel).RawValue); /// Gets a packed value representing all the allowed permissions in this OverwritePermissions. - public uint AllowValue { get; } + public ulong AllowValue { get; } /// Gets a packed value representing all the denied permissions in this OverwritePermissions. - public uint DenyValue { get; } + public ulong DenyValue { get; } /// If True, a user may create invites. - public PermValue CreateInstantInvite => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); + public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); /// If True, a user may create, delete and modify this channel. - public PermValue ManageChannel => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel); + public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel); /// If True, a user may join channels. - public PermValue ReadMessages => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessages); + public PermValue ReadMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessages); /// If True, a user may send messages. - public PermValue SendMessages => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); + public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); /// If True, a user may send text-to-speech messages. - public PermValue SendTTSMessages => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.SendTTSMessages); + public PermValue SendTTSMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendTTSMessages); /// If True, a user may delete messages. - public PermValue ManageMessages => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.ManageMessages); + public PermValue ManageMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageMessages); /// If True, Discord will auto-embed links sent by this user. - public PermValue EmbedLinks => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.EmbedLinks); + public PermValue EmbedLinks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.EmbedLinks); /// If True, a user may send files. - public PermValue AttachFiles => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.AttachFiles); + public PermValue AttachFiles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AttachFiles); /// If True, a user may read previous messages. - public PermValue ReadMessageHistory => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessageHistory); + public PermValue ReadMessageHistory => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessageHistory); /// If True, a user may mention @everyone. - public PermValue MentionEveryone => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.MentionEveryone); + public PermValue MentionEveryone => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MentionEveryone); /// If True, a user may connect to a voice channel. - public PermValue Connect => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.Connect); + public PermValue Connect => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.Connect); /// If True, a user may speak in a voice channel. - public PermValue Speak => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.Speak); + public PermValue Speak => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.Speak); /// If True, a user may mute users. - public PermValue MuteMembers => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.MuteMembers); + public PermValue MuteMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MuteMembers); /// If True, a user may deafen users. - public PermValue DeafenMembers => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.DeafenMembers); + public PermValue DeafenMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.DeafenMembers); /// If True, a user may move other users between voice channels. - public PermValue MoveMembers => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.MoveMembers); + public PermValue MoveMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MoveMembers); /// If True, a user may use voice-activity-detection rather than push-to-talk. - public PermValue UseVAD => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); + public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); /// If True, a user may adjust permissions. This also implictly grants all other permissions. - public PermValue ManagePermissions => PermissionUtilities.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions); + public PermValue ManagePermissions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions); /// Creates a new OverwritePermissions with the provided allow and deny packed values. - public OverwritePermissions(uint allowValue, uint denyValue) + public OverwritePermissions(ulong allowValue, ulong denyValue) { AllowValue = allowValue; DenyValue = denyValue; } - private OverwritePermissions(uint allowValue, uint denyValue, PermValue? createInstantInvite = null, PermValue? manageChannel = null, + private OverwritePermissions(ulong allowValue, ulong denyValue, PermValue? createInstantInvite = null, PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null) { - PermissionUtilities.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannel); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, sendTTSMessages, ChannelPermission.SendTTSMessages); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, manageMessages, ChannelPermission.ManageMessages); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, embedLinks, ChannelPermission.EmbedLinks); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, attachFiles, ChannelPermission.AttachFiles); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, readMessageHistory, ChannelPermission.ReadMessageHistory); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, mentionEveryone, ChannelPermission.MentionEveryone); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, connect, ChannelPermission.Connect); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, speak, ChannelPermission.Speak); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, muteMembers, ChannelPermission.MuteMembers); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); - PermissionUtilities.SetValue(ref allowValue, ref denyValue, managePermissions, ChannelPermission.ManagePermissions); + Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); + Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannel); + Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); + Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); + Permissions.SetValue(ref allowValue, ref denyValue, sendTTSMessages, ChannelPermission.SendTTSMessages); + Permissions.SetValue(ref allowValue, ref denyValue, manageMessages, ChannelPermission.ManageMessages); + Permissions.SetValue(ref allowValue, ref denyValue, embedLinks, ChannelPermission.EmbedLinks); + Permissions.SetValue(ref allowValue, ref denyValue, attachFiles, ChannelPermission.AttachFiles); + Permissions.SetValue(ref allowValue, ref denyValue, readMessageHistory, ChannelPermission.ReadMessageHistory); + Permissions.SetValue(ref allowValue, ref denyValue, mentionEveryone, ChannelPermission.MentionEveryone); + Permissions.SetValue(ref allowValue, ref denyValue, connect, ChannelPermission.Connect); + Permissions.SetValue(ref allowValue, ref denyValue, speak, ChannelPermission.Speak); + Permissions.SetValue(ref allowValue, ref denyValue, muteMembers, ChannelPermission.MuteMembers); + Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); + Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); + Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); + Permissions.SetValue(ref allowValue, ref denyValue, managePermissions, ChannelPermission.ManagePermissions); AllowValue = allowValue; DenyValue = denyValue; @@ -115,8 +115,8 @@ namespace Discord public override string ToString() { var perms = new List(); - int x = 1; - for (byte i = 0; i < 32; i++, x <<= 1) + ulong x = 1; + for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((AllowValue & x) != 0) { diff --git a/src/Discord.Net/Common/Entities/Permissions/PermissionUtilities.cs b/src/Discord.Net/Common/Entities/Permissions/PermissionUtilities.cs deleted file mode 100644 index 66ad57374..000000000 --- a/src/Discord.Net/Common/Entities/Permissions/PermissionUtilities.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Discord -{ - internal static class PermissionUtilities - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(uint allow, uint deny, ChannelPermission bit) - => GetValue(allow, deny, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(uint allow, uint deny, GuildPermission bit) - => GetValue(allow, deny, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PermValue GetValue(uint allow, uint deny, byte bit) - { - if (HasBit(allow, bit)) - return PermValue.Allow; - else if (HasBit(deny, bit)) - return PermValue.Deny; - else - return PermValue.Inherit; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(uint value, ChannelPermission bit) - => GetValue(value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(uint value, GuildPermission bit) - => GetValue(value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GetValue(uint value, byte bit) => HasBit(value, bit); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint rawValue, bool? value, ChannelPermission bit) - => SetValue(ref rawValue, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint rawValue, bool? value, GuildPermission bit) - => SetValue(ref rawValue, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint rawValue, bool? value, byte bit) - { - if (value.HasValue) - { - if (value == true) - SetBit(ref rawValue, bit); - else - UnsetBit(ref rawValue, bit); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint allow, ref uint deny, PermValue? value, ChannelPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint allow, ref uint deny, PermValue? value, GuildPermission bit) - => SetValue(ref allow, ref deny, value, (byte)bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetValue(ref uint allow, ref uint deny, PermValue? value, byte bit) - { - if (value.HasValue) - { - switch (value) - { - case PermValue.Allow: - SetBit(ref allow, bit); - UnsetBit(ref deny, bit); - break; - case PermValue.Deny: - UnsetBit(ref allow, bit); - SetBit(ref deny, bit); - break; - default: - UnsetBit(ref allow, bit); - UnsetBit(ref deny, bit); - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool HasBit(uint value, byte bit) => (value & (1U << bit)) != 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetBit(ref uint value, byte bit) => value |= (1U << bit); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsetBit(ref uint value, byte bit) => value &= ~(1U << bit); - } -} diff --git a/src/Discord.Net/Common/Entities/Permissions/Permissions.cs b/src/Discord.Net/Common/Entities/Permissions/Permissions.cs new file mode 100644 index 000000000..235437fe2 --- /dev/null +++ b/src/Discord.Net/Common/Entities/Permissions/Permissions.cs @@ -0,0 +1,152 @@ +using System.Runtime.CompilerServices; + +namespace Discord +{ + internal static class Permissions + { + public const int MaxBits = 53; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) + => GetValue(allow, deny, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) + => GetValue(allow, deny, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(ulong allow, ulong deny, byte bit) + { + if (HasBit(allow, bit)) + return PermValue.Allow; + else if (HasBit(deny, bit)) + return PermValue.Deny; + else + return PermValue.Inherit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, ChannelPermission bit) + => GetValue(value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, GuildPermission bit) + => GetValue(value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) + => SetValue(ref rawValue, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) + => SetValue(ref rawValue, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong rawValue, bool? value, byte bit) + { + if (value.HasValue) + { + if (value == true) + SetBit(ref rawValue, bit); + else + UnsetBit(ref rawValue, bit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) + => SetValue(ref allow, ref deny, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) + => SetValue(ref allow, ref deny, value, (byte)bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) + { + if (value.HasValue) + { + switch (value) + { + case PermValue.Allow: + SetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + case PermValue.Deny: + UnsetBit(ref allow, bit); + SetBit(ref deny, bit); + break; + default: + UnsetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); + + public static ulong ResolveGuild(IGuildUser user) + { + var roles = user.Roles; + ulong newPermissions = 0; + for (int i = 0; i < roles.Count; i++) + newPermissions |= roles[i].Permissions.RawValue; + return newPermissions; + } + + /*public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel) + { + return ResolveChannel(user, channel, ResolveGuild(user)); + }*/ + public static ulong ResolveChannel(IGuildUser user, IGuildChannel channel, ulong guildPermissions) + { + ulong resolvedPermissions = 0; + + ulong mask = ChannelPermissions.All(channel).RawValue; + if (user.Id == user.Guild.OwnerId || GetValue(resolvedPermissions, GuildPermission.Administrator)) + resolvedPermissions = mask; //Owners and administrators always have all permissions + else + { + //Start with this user's guild permissions + resolvedPermissions = guildPermissions; + var overwrites = channel.PermissionOverwrites; + + Overwrite entry; + var roles = user.Roles; + if (roles.Count > 0) + { + for (int i = 0; i < roles.Count; i++) + { + if (overwrites.TryGetValue(roles[i].Id, out entry)) + resolvedPermissions &= ~entry.Permissions.DenyValue; + } + for (int i = 0; i < roles.Count; i++) + { + if (overwrites.TryGetValue(roles[i].Id, out entry)) + resolvedPermissions |= entry.Permissions.AllowValue; + } + } + if (overwrites.TryGetValue(user.Id, out entry)) + resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue; + + switch (channel) + { + case ITextChannel _: + if (!GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) + resolvedPermissions = 0; //No read permission on a text channel removes all other permissions + break; + case IVoiceChannel _: + if (!GetValue(resolvedPermissions, ChannelPermission.Connect)) + resolvedPermissions = 0; //No read permission on a text channel removes all other permissions + break; + default: + resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) + break; + } + } + + return resolvedPermissions; + } + } +} diff --git a/src/Discord.Net/Common/Helpers/EventExtensions.cs b/src/Discord.Net/Common/EventExtensions.cs similarity index 100% rename from src/Discord.Net/Common/Helpers/EventExtensions.cs rename to src/Discord.Net/Common/EventExtensions.cs diff --git a/src/Discord.Net/Common/Helpers/PermissionHelper.cs b/src/Discord.Net/Common/Helpers/PermissionHelper.cs deleted file mode 100644 index 780aedbf8..000000000 --- a/src/Discord.Net/Common/Helpers/PermissionHelper.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace Discord -{ - public static class PermissionHelper - { - public static uint Resolve(IGuildUser user) - { - var roles = user.Roles; - uint newPermissions = 0; - for (int i = 0; i < roles.Count; i++) - newPermissions |= roles[i].Permissions.RawValue; - return newPermissions; - } - - public static uint Resolve(IGuildUser user, IGuildChannel channel) - { - uint resolvedPermissions = 0; - - uint mask = ChannelPermissions.All(channel).RawValue; - if (user.Id == user.Guild.OwnerId || PermissionUtilities.GetValue(resolvedPermissions, GuildPermission.Administrator)) - resolvedPermissions = mask; //Owners and administrators always have all permissions - else - { - //Start with this user's guild permissions - resolvedPermissions = Resolve(user); - var overwrites = channel.PermissionOverwrites; - - Overwrite entry; - var roles = user.Roles; - if (roles.Count > 0) - { - for (int i = 0; i < roles.Count; i++) - { - if (overwrites.TryGetValue(roles[i].Id, out entry)) - resolvedPermissions &= ~entry.Permissions.DenyValue; - } - for (int i = 0; i < roles.Count; i++) - { - if (overwrites.TryGetValue(roles[i].Id, out entry)) - resolvedPermissions |= entry.Permissions.AllowValue; - } - } - if (overwrites.TryGetValue(user.Id, out entry)) - resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue; - - switch (channel) - { - case ITextChannel _: - if (!PermissionUtilities.GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) - resolvedPermissions = 0; //No read permission on a text channel removes all other permissions - break; - case IVoiceChannel _: - if (!PermissionUtilities.GetValue(resolvedPermissions, ChannelPermission.Connect)) - resolvedPermissions = 0; //No read permission on a text channel removes all other permissions - break; - default: - resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) - break; - } - } - - return resolvedPermissions; - } - } -} diff --git a/src/Discord.Net/Common/Helpers/MentionHelper.cs b/src/Discord.Net/Common/MentionUtils.cs similarity index 99% rename from src/Discord.Net/Common/Helpers/MentionHelper.cs rename to src/Discord.Net/Common/MentionUtils.cs index d2e350b2b..7e4aba6d2 100644 --- a/src/Discord.Net/Common/Helpers/MentionHelper.cs +++ b/src/Discord.Net/Common/MentionUtils.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; namespace Discord { - public static class MentionHelper + public static class MentionUtils { private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled); private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled); diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index af5ce3e12..e8fc2649c 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -150,7 +150,7 @@ - + @@ -166,7 +166,6 @@ - @@ -177,9 +176,9 @@ - - - + + + diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index 57e684493..90603d24e 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -19,7 +19,7 @@ namespace Discord.Rest public DMUser Recipient { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); internal DMChannel(DiscordClient discord, Model model) { diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index 635709948..7a07679c8 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -23,7 +23,7 @@ namespace Discord.Rest public int Position { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public IReadOnlyDictionary PermissionOverwrites => _overwrites; internal DiscordClient Discord => Guild.Discord; diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index 877483645..5b4eb0d82 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -14,7 +14,7 @@ namespace Discord.Rest public string Topic { get; private set; } /// - public string Mention => MentionHelper.Mention(this); + public string Mention => MentionUtils.Mention(this); internal TextChannel(Guild guild, Model model) : base(guild, model) @@ -40,14 +40,14 @@ namespace Discord.Rest public override async Task GetUser(ulong id) { var user = await Guild.GetUser(id).ConfigureAwait(false); - if (user != null && PermissionUtilities.GetValue(PermissionHelper.Resolve(user, this), ChannelPermission.ReadMessages)) + if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) return user; return null; } public override async Task> GetUsers() { var users = await Guild.GetUsers().ConfigureAwait(false); - return users.Where(x => PermissionUtilities.GetValue(PermissionHelper.Resolve(x, this), ChannelPermission.ReadMessages)); + return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)); } /// diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index c1ee23236..421157b7d 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -44,7 +44,7 @@ namespace Discord.Rest public IReadOnlyList Features { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); /// diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs index d73d45a2e..f53c621af 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs @@ -13,7 +13,7 @@ namespace Discord public ulong? ChannelId { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); internal GuildEmbed(Model model) { diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs index 596719d7b..cead4e794 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs @@ -18,7 +18,7 @@ namespace Discord public GuildPermissions Permissions { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index fb74970bf..3e5dd5012 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -40,7 +40,7 @@ namespace Discord.Rest public IReadOnlyList MentionedRoleIds { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; internal Message(IMessageChannel channel, Model model) @@ -88,8 +88,8 @@ namespace Discord.Rest } else MentionedUsers = Array.Empty(); - MentionedChannelIds = MentionHelper.GetChannelMentions(model.Content); - MentionedRoleIds = MentionHelper.GetRoleMentions(model.Content); + MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); + MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); if (model.IsMentioningEveryone) { ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; @@ -107,7 +107,7 @@ namespace Discord.Rest } } - Text = MentionHelper.CleanUserMentions(model.Content, model.Mentions); + Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); Author.Update(model.Author); } diff --git a/src/Discord.Net/Rest/Entities/Role.cs b/src/Discord.Net/Rest/Entities/Role.cs index 7e77f455c..b22f3f9ea 100644 --- a/src/Discord.Net/Rest/Entities/Role.cs +++ b/src/Discord.Net/Rest/Entities/Role.cs @@ -28,11 +28,11 @@ namespace Discord.Rest public int Position { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public bool IsEveryone => Id == Guild.Id; /// - public string Mention => MentionHelper.Mention(this); + public string Mention => MentionUtils.Mention(this); internal DiscordClient Discord => Guild.Discord; internal Role(Guild guild, Model model) diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index d36d094fb..bc6dfa7a2 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -23,6 +23,9 @@ namespace Discord.Rest /// public string Nickname { get; private set; } + /// + public GuildPermissions GuildPermissions { get; private set; } + /// public IReadOnlyList Roles => _roles; internal override DiscordClient Discord => Guild.Discord; @@ -44,6 +47,8 @@ namespace Discord.Rest for (int i = 0; i < model.Roles.Length; i++) roles.Add(Guild.GetRole(model.Roles[i])); _roles = roles.ToImmutable(); + + GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); } public async Task Update() @@ -67,14 +72,10 @@ namespace Discord.Rest await Discord.BaseClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); } - public GuildPermissions GetGuildPermissions() - { - return new GuildPermissions(PermissionHelper.Resolve(this)); - } public ChannelPermissions GetPermissions(IGuildChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); - return new ChannelPermissions(PermissionHelper.Resolve(this, channel)); + return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); } public async Task Modify(Action func) @@ -111,6 +112,8 @@ namespace Discord.Rest IReadOnlyList IGuildUser.Roles => Roles; IVoiceChannel IGuildUser.VoiceChannel => null; + GuildPermissions IGuildUser.GetGuildPermissions() + => GuildPermissions; ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => GetPermissions(channel); } diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index 0fe02bacc..622bb6517 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -23,11 +23,11 @@ namespace Discord.Rest /// public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// - public string Mention => MentionHelper.Mention(this, false); + public string Mention => MentionUtils.Mention(this, false); /// - public string NicknameMention => MentionHelper.Mention(this, true); + public string NicknameMention => MentionUtils.Mention(this, true); internal User(Model model) { diff --git a/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs b/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs index 4a79d23fc..fcf5ff6a8 100644 --- a/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs +++ b/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs @@ -39,7 +39,7 @@ namespace Discord.WebSocket } public void Add(GuildUser user) { - _users[user.Id] = new ChannelMember(user, new ChannelPermissions(PermissionHelper.Resolve(user, _channel))); + _users[user.Id] = new ChannelMember(user, new ChannelPermissions(Permissions.ResolveChannel(user, _channel, user.GuildPermissions.RawValue))); } public void Remove(GuildUser user) { @@ -52,7 +52,7 @@ namespace Discord.WebSocket foreach (var pair in _users) { var member = pair.Value; - var newPerms = PermissionHelper.Resolve(member.User, _channel); + var newPerms = Permissions.ResolveChannel(member.User, _channel, member.User.GuildPermissions.RawValue); if (newPerms != member.Permissions.RawValue) _users[pair.Key] = new ChannelMember(member.User, new ChannelPermissions(newPerms)); } @@ -62,7 +62,7 @@ namespace Discord.WebSocket ChannelMember member; if (_users.TryGetValue(user.Id, out member)) { - var newPerms = PermissionHelper.Resolve(user, _channel); + var newPerms = Permissions.ResolveChannel(user, _channel, user.GuildPermissions.RawValue); if (newPerms != member.Permissions.RawValue) _users[user.Id] = new ChannelMember(user, new ChannelPermissions(newPerms)); } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index 84eb36916..14deb2c52 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -21,7 +21,7 @@ namespace Discord.WebSocket public DMUser Recipient { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public IEnumerable Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); public IEnumerable CachedMessages => _messages.Messages; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 7f33e1eae..8a364302a 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -25,7 +25,7 @@ namespace Discord.WebSocket public abstract IEnumerable Users { get; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public IReadOnlyDictionary PermissionOverwrites => _overwrites; internal DiscordClient Discord => Guild.Discord; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index 0d8199bb7..b9176da28 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -17,7 +17,7 @@ namespace Discord.WebSocket public string Topic { get; private set; } /// - public string Mention => MentionHelper.Mention(this); + public string Mention => MentionUtils.Mention(this); public override IEnumerable Users => _permissions.Members.Where(x => x.Permissions.ReadMessages).Select(x => x.User).ToImmutableArray(); public IEnumerable CachedMessages => _messages.Messages; diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index fa5bba1c0..519a4b869 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -45,7 +45,7 @@ namespace Discord.WebSocket public IReadOnlyList Features { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); /// diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index 5ab17ac5d..a68146a44 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -40,7 +40,7 @@ namespace Discord.WebSocket public IReadOnlyList MentionedRoleIds { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; internal Message(IMessageChannel channel, Model model) @@ -88,8 +88,8 @@ namespace Discord.WebSocket } else MentionedUsers = Array.Empty(); - MentionedChannelIds = MentionHelper.GetChannelMentions(model.Content); - MentionedRoleIds = MentionHelper.GetRoleMentions(model.Content); + MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); + MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); if (model.IsMentioningEveryone) { ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; @@ -107,7 +107,7 @@ namespace Discord.WebSocket } } - Text = MentionHelper.CleanUserMentions(model.Content, model.Mentions); + Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); Author.Update(model.Author); } diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs index 9a8ca2bcc..6844cd41f 100644 --- a/src/Discord.Net/WebSocket/Entities/Role.cs +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -28,11 +28,11 @@ namespace Discord.WebSocket public int Position { get; private set; } /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// public bool IsEveryone => Id == Guild.Id; /// - public string Mention => MentionHelper.Mention(this); + public string Mention => MentionUtils.Mention(this); internal DiscordClient Discord => Guild.Discord; internal Role(Guild guild, Model model) diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 919d27fe1..57b065a50 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -24,6 +24,7 @@ namespace Discord.WebSocket public string Nickname { get; private set; } /// public VoiceChannel VoiceChannel { get; private set; } + public GuildPermissions GuildPermissions { get; private set; } /// public IReadOnlyList Roles => _roles; @@ -46,6 +47,12 @@ namespace Discord.WebSocket for (int i = 0; i < model.Roles.Length; i++) roles.Add(Guild.GetRole(model.Roles[i])); _roles = roles.ToImmutable(); + + UpdateGuildPermissions(); + } + internal void UpdateGuildPermissions() + { + GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); } public bool HasRole(IRole role) @@ -65,12 +72,12 @@ namespace Discord.WebSocket public GuildPermissions GetGuildPermissions() { - return new GuildPermissions(PermissionHelper.Resolve(this)); + return new GuildPermissions(Permissions.ResolveGuild(this)); } public ChannelPermissions GetPermissions(IGuildChannel channel) { if (channel == null) throw new ArgumentNullException(nameof(channel)); - return new ChannelPermissions(PermissionHelper.Resolve(this, channel)); + return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); } public async Task Modify(Action func) diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index b757cd003..42615dc65 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -23,11 +23,11 @@ namespace Discord.WebSocket /// public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); /// - public DateTime CreatedAt => DateTimeHelper.FromSnowflake(Id); + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); /// - public string Mention => MentionHelper.Mention(this, false); + public string Mention => MentionUtils.Mention(this, false); /// - public string NicknameMention => MentionHelper.Mention(this, true); + public string NicknameMention => MentionUtils.Mention(this, true); internal User(Model model) { From 1ee5e6d7716c519e65747d13016798b4c284ec92 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 02:07:52 -0300 Subject: [PATCH 08/39] Explicitly select json converters, added Image and Int53 attributes, fixed a couple of routes. --- src/Discord.Net/API/Common/Overwrite.cs | 4 +- src/Discord.Net/API/Common/Role.cs | 2 +- src/Discord.Net/API/Common/UserGuild.cs | 4 +- src/Discord.Net/API/DiscordRawClient.cs | 22 ++----- src/Discord.Net/API/ImageAttribute.cs | 7 ++ src/Discord.Net/API/Int53Attribute.cs | 7 ++ src/Discord.Net/API/Rest/CreateGuildParams.cs | 2 +- .../API/Rest/ModifyCurrentUserParams.cs | 2 +- src/Discord.Net/API/Rest/ModifyGuildParams.cs | 4 +- src/Discord.Net/Discord.Net.csproj | 4 +- .../Net/Converters/ChannelTypeConverter.cs | 4 +- .../Net/Converters/DiscordContractResolver.cs | 65 +++++++++++++++++++ .../Net/Converters/ImageConverter.cs | 4 +- .../Net/Converters/NullableUInt64Converter.cs | 4 +- .../Converters/OptionalContractResolver.cs | 34 ---------- .../Net/Converters/OptionalConverter.cs | 6 +- .../Converters/PermissionTargetConverter.cs | 4 +- .../Net/Converters/StringEntityConverter.cs | 4 +- .../Net/Converters/UInt64ArrayConverter.cs | 4 +- .../Net/Converters/UInt64Converter.cs | 4 +- .../Net/Converters/UInt64EntityConverter.cs | 7 +- .../Net/Converters/UserStatusConverter.cs | 4 +- 22 files changed, 132 insertions(+), 70 deletions(-) create mode 100644 src/Discord.Net/API/ImageAttribute.cs create mode 100644 src/Discord.Net/API/Int53Attribute.cs create mode 100644 src/Discord.Net/Net/Converters/DiscordContractResolver.cs delete mode 100644 src/Discord.Net/Net/Converters/OptionalContractResolver.cs diff --git a/src/Discord.Net/API/Common/Overwrite.cs b/src/Discord.Net/API/Common/Overwrite.cs index ab9799f71..0d041e163 100644 --- a/src/Discord.Net/API/Common/Overwrite.cs +++ b/src/Discord.Net/API/Common/Overwrite.cs @@ -8,9 +8,9 @@ namespace Discord.API public ulong TargetId { get; set; } [JsonProperty("type")] public PermissionTarget TargetType { get; set; } - [JsonProperty("deny")] + [JsonProperty("deny"), Int53] public ulong Deny { get; set; } - [JsonProperty("allow")] + [JsonProperty("allow"), Int53] public ulong Allow { get; set; } } } diff --git a/src/Discord.Net/API/Common/Role.cs b/src/Discord.Net/API/Common/Role.cs index 55f1933b7..721b2a50b 100644 --- a/src/Discord.Net/API/Common/Role.cs +++ b/src/Discord.Net/API/Common/Role.cs @@ -14,7 +14,7 @@ namespace Discord.API public bool? Hoist { get; set; } [JsonProperty("position")] public int? Position { get; set; } - [JsonProperty("permissions")] + [JsonProperty("permissions"), Int53] public ulong? Permissions { get; set; } [JsonProperty("managed")] public bool? Managed { get; set; } diff --git a/src/Discord.Net/API/Common/UserGuild.cs b/src/Discord.Net/API/Common/UserGuild.cs index 124f64688..7eaefca39 100644 --- a/src/Discord.Net/API/Common/UserGuild.cs +++ b/src/Discord.Net/API/Common/UserGuild.cs @@ -12,7 +12,7 @@ namespace Discord.API public string Icon { get; set; } [JsonProperty("owner")] public bool Owner { get; set; } - [JsonProperty("permissions")] - public uint Permissions { get; set; } + [JsonProperty("permissions"), Int53] + public ulong Permissions { get; set; } } } diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 218964937..64055db84 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -29,7 +29,7 @@ namespace Discord.API public TokenType AuthTokenType { get; private set; } public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - + internal DiscordRawClient(RestClientProvider restClientProvider) { _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); @@ -38,18 +38,10 @@ namespace Discord.API _requestQueue = new RequestQueue(_restClient); - _serializer = new JsonSerializer(); - _serializer.Converters.Add(new OptionalConverter()); - _serializer.Converters.Add(new ChannelTypeConverter()); - _serializer.Converters.Add(new ImageConverter()); - _serializer.Converters.Add(new NullableUInt64Converter()); - _serializer.Converters.Add(new PermissionTargetConverter()); - _serializer.Converters.Add(new StringEntityConverter()); - _serializer.Converters.Add(new UInt64ArrayConverter()); - _serializer.Converters.Add(new UInt64Converter()); - _serializer.Converters.Add(new UInt64EntityConverter()); - _serializer.Converters.Add(new UserStatusConverter()); - _serializer.ContractResolver = new OptionalContractResolver(); + _serializer = new JsonSerializer() + { + ContractResolver = new DiscordContractResolver() + }; } public async Task Login(TokenType tokenType, string token, CancellationToken cancelToken) @@ -202,7 +194,7 @@ namespace Discord.API { if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - return await Send>("GET", $"guild/{guildId}/channels").ConfigureAwait(false); + return await Send>("GET", $"guilds/{guildId}/channels").ConfigureAwait(false); } public async Task CreateGuildChannel(ulong guildId, CreateGuildChannelParams args) { @@ -539,7 +531,7 @@ namespace Discord.API { if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - return await Send>("GET", $"guild/{guildId}/roles").ConfigureAwait(false); + return await Send>("GET", $"guilds/{guildId}/roles").ConfigureAwait(false); } public async Task CreateGuildRole(ulong guildId) { diff --git a/src/Discord.Net/API/ImageAttribute.cs b/src/Discord.Net/API/ImageAttribute.cs new file mode 100644 index 000000000..c31c6d982 --- /dev/null +++ b/src/Discord.Net/API/ImageAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Discord.API +{ + [AttributeUsage(AttributeTargets.Property)] + public class ImageAttribute : Attribute { } +} diff --git a/src/Discord.Net/API/Int53Attribute.cs b/src/Discord.Net/API/Int53Attribute.cs new file mode 100644 index 000000000..7ac6f5467 --- /dev/null +++ b/src/Discord.Net/API/Int53Attribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Discord.API +{ + [AttributeUsage(AttributeTargets.Property)] + public class Int53Attribute : Attribute { } +} diff --git a/src/Discord.Net/API/Rest/CreateGuildParams.cs b/src/Discord.Net/API/Rest/CreateGuildParams.cs index dd6e5b8fd..6cf0ed149 100644 --- a/src/Discord.Net/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net/API/Rest/CreateGuildParams.cs @@ -11,7 +11,7 @@ namespace Discord.API.Rest [JsonProperty("region")] public string Region { get; set; } - [JsonProperty("icon"), JsonConverter(typeof(ImageConverter))] + [JsonProperty("icon"), Image] public Optional Icon { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs index 64aab3181..a29a9c8b4 100644 --- a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs @@ -14,7 +14,7 @@ namespace Discord.API.Rest public Optional Password { get; set; } [JsonProperty("new_password")] public Optional NewPassword { get; set; } - [JsonProperty("avatar"), JsonConverter(typeof(ImageConverter))] + [JsonProperty("avatar"), Image] public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildParams.cs b/src/Discord.Net/API/Rest/ModifyGuildParams.cs index e92b1f63c..6e7ff2e34 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildParams.cs @@ -16,11 +16,11 @@ namespace Discord.API.Rest public Optional AFKChannelId { get; set; } [JsonProperty("afk_timeout")] public Optional AFKTimeout { get; set; } - [JsonProperty("icon"), JsonConverter(typeof(ImageConverter))] + [JsonProperty("icon"), Image] public Optional Icon { get; set; } [JsonProperty("owner_id")] public Optional Owner { get; set; } - [JsonProperty("splash"), JsonConverter(typeof(ImageConverter))] + [JsonProperty("splash"), Image] public Optional Splash { get; set; } } } diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index e8fc2649c..f5e9372e3 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -67,6 +67,7 @@ + @@ -105,7 +106,8 @@ - + + diff --git a/src/Discord.Net/Net/Converters/ChannelTypeConverter.cs b/src/Discord.Net/Net/Converters/ChannelTypeConverter.cs index 95c4479df..48bcbd755 100644 --- a/src/Discord.Net/Net/Converters/ChannelTypeConverter.cs +++ b/src/Discord.Net/Net/Converters/ChannelTypeConverter.cs @@ -5,7 +5,9 @@ namespace Discord.Net.Converters { public class ChannelTypeConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(ChannelType); + public static readonly ChannelTypeConverter Instance = new ChannelTypeConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net/Net/Converters/DiscordContractResolver.cs new file mode 100644 index 000000000..a77d3cf28 --- /dev/null +++ b/src/Discord.Net/Net/Converters/DiscordContractResolver.cs @@ -0,0 +1,65 @@ +using Discord.API; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Discord.Net.Converters +{ + public class DiscordContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + var type = property.PropertyType; + JsonConverter converter = null; + + if (member.MemberType == MemberTypes.Property) + { + //Primitives + if (type == typeof(ulong) && member.GetCustomAttribute() == null) + converter = UInt64Converter.Instance; + else if (type == typeof(ulong?) && member.GetCustomAttribute() == null) + converter = NullableUInt64Converter.Instance; + else if (typeof(IEnumerable).IsAssignableFrom(type) && member.GetCustomAttribute() == null) + converter = NullableUInt64Converter.Instance; + + //Enums + else if (type == typeof(ChannelType)) + converter = ChannelTypeConverter.Instance; + else if (type == typeof(PermissionTarget)) + converter = PermissionTargetConverter.Instance; + else if (type == typeof(UserStatus)) + converter = UserStatusConverter.Instance; + + //Entities + else if (typeof(IEntity).IsAssignableFrom(type)) + converter = UInt64EntityConverter.Instance; + else if (typeof(IEntity).IsAssignableFrom(type)) + converter = StringEntityConverter.Instance; + + //Special + else if (type == typeof(string) && member.GetCustomAttribute() != null) + converter = ImageConverter.Instance; + else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) + { + var parentArg = Expression.Parameter(typeof(object)); + var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); + var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty); + var lambda = Expression.Lambda>(isSpecified, parentArg).Compile(); + property.ShouldSerialize = x => lambda(x); + converter = OptionalConverter.Instance; + } + } + if (converter != null) + { + property.Converter = converter; + property.MemberConverter = converter; + } + + return property; + } + } +} diff --git a/src/Discord.Net/Net/Converters/ImageConverter.cs b/src/Discord.Net/Net/Converters/ImageConverter.cs index 5fc25e8d2..a40b5bf86 100644 --- a/src/Discord.Net/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net/Net/Converters/ImageConverter.cs @@ -7,7 +7,9 @@ namespace Discord.Net.Converters { public class ImageConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(Stream) || objectType == typeof(Optional); + public static readonly ImageConverter Instance = new ImageConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/NullableUInt64Converter.cs b/src/Discord.Net/Net/Converters/NullableUInt64Converter.cs index e28460abc..050ac7c32 100644 --- a/src/Discord.Net/Net/Converters/NullableUInt64Converter.cs +++ b/src/Discord.Net/Net/Converters/NullableUInt64Converter.cs @@ -6,7 +6,9 @@ namespace Discord.Net.Converters { public class NullableUInt64Converter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(ulong?); + public static readonly NullableUInt64Converter Instance = new NullableUInt64Converter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/OptionalContractResolver.cs b/src/Discord.Net/Net/Converters/OptionalContractResolver.cs deleted file mode 100644 index cc0705671..000000000 --- a/src/Discord.Net/Net/Converters/OptionalContractResolver.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Discord.API; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; -using System.Linq.Expressions; -using System.Reflection; - -namespace Discord.Net.Converters -{ - public class OptionalContractResolver : DefaultContractResolver - { - private static readonly PropertyInfo _isSpecified = typeof(IOptional).GetProperty(nameof(IOptional.IsSpecified)); - - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - var type = property.PropertyType; - - if (member.MemberType == MemberTypes.Property) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) - { - var parentArg = Expression.Parameter(typeof(object)); - var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); - var isSpecified = Expression.Property(optional, _isSpecified); - var lambda = Expression.Lambda>(isSpecified, parentArg).Compile(); - property.ShouldSerialize = x => lambda(x); - } - } - - return property; - } - } -} diff --git a/src/Discord.Net/Net/Converters/OptionalConverter.cs b/src/Discord.Net/Net/Converters/OptionalConverter.cs index e75769e5f..44c670d76 100644 --- a/src/Discord.Net/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net/Net/Converters/OptionalConverter.cs @@ -1,12 +1,16 @@ using Discord.API; using Newtonsoft.Json; using System; +using System.Reflection; namespace Discord.Net.Converters { public class OptionalConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>); + public static readonly OptionalConverter Instance = new OptionalConverter(); + internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetProperty(nameof(IOptional.IsSpecified)); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net/Net/Converters/PermissionTargetConverter.cs index a7ef55409..6dc74932f 100644 --- a/src/Discord.Net/Net/Converters/PermissionTargetConverter.cs +++ b/src/Discord.Net/Net/Converters/PermissionTargetConverter.cs @@ -5,7 +5,9 @@ namespace Discord.Net.Converters { public class PermissionTargetConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(PermissionTarget); + public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/StringEntityConverter.cs b/src/Discord.Net/Net/Converters/StringEntityConverter.cs index 49c52bd9d..902fb1a75 100644 --- a/src/Discord.Net/Net/Converters/StringEntityConverter.cs +++ b/src/Discord.Net/Net/Converters/StringEntityConverter.cs @@ -5,7 +5,9 @@ namespace Discord.Net.Converters { public class StringEntityConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(IEntity); + public static readonly StringEntityConverter Instance = new StringEntityConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs b/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs index 8e94b51f5..d0a8d170b 100644 --- a/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs +++ b/src/Discord.Net/Net/Converters/UInt64ArrayConverter.cs @@ -7,7 +7,9 @@ namespace Discord.Net.Converters { public class UInt64ArrayConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); + public static readonly UInt64ArrayConverter Instance = new UInt64ArrayConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/UInt64Converter.cs b/src/Discord.Net/Net/Converters/UInt64Converter.cs index 4983759ab..6cbcd81f6 100644 --- a/src/Discord.Net/Net/Converters/UInt64Converter.cs +++ b/src/Discord.Net/Net/Converters/UInt64Converter.cs @@ -6,7 +6,9 @@ namespace Discord.Net.Converters { public class UInt64Converter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(ulong); + public static readonly UInt64Converter Instance = new UInt64Converter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; diff --git a/src/Discord.Net/Net/Converters/UInt64EntityConverter.cs b/src/Discord.Net/Net/Converters/UInt64EntityConverter.cs index 6a0705e3a..8a102ab22 100644 --- a/src/Discord.Net/Net/Converters/UInt64EntityConverter.cs +++ b/src/Discord.Net/Net/Converters/UInt64EntityConverter.cs @@ -1,11 +1,14 @@ using Newtonsoft.Json; using System; +using System.Globalization; namespace Discord.Net.Converters { public class UInt64EntityConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(IEntity); + public static readonly UInt64EntityConverter Instance = new UInt64EntityConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; public override bool CanWrite => true; @@ -17,7 +20,7 @@ namespace Discord.Net.Converters public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value != null) - writer.WriteValue((value as IEntity).Id); + writer.WriteValue((value as IEntity).Id.ToString(CultureInfo.InvariantCulture)); else writer.WriteNull(); } diff --git a/src/Discord.Net/Net/Converters/UserStatusConverter.cs b/src/Discord.Net/Net/Converters/UserStatusConverter.cs index 7ed690421..d2c25d3b8 100644 --- a/src/Discord.Net/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net/Net/Converters/UserStatusConverter.cs @@ -5,7 +5,9 @@ namespace Discord.Net.Converters { public class UserStatusConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(UserStatus); + public static readonly UserStatusConverter Instance = new UserStatusConverter(); + + public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; From 4365aced85a5a6d82aadddb244aa3ba59dd50f5d Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 02:14:41 -0300 Subject: [PATCH 09/39] Change LastMessageId to nullable --- src/Discord.Net/API/Common/Channel.cs | 2 +- src/Discord.Net/API/Common/ReadState.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/API/Common/Channel.cs b/src/Discord.Net/API/Common/Channel.cs index 6dda88944..e724f1285 100644 --- a/src/Discord.Net/API/Common/Channel.cs +++ b/src/Discord.Net/API/Common/Channel.cs @@ -10,7 +10,7 @@ namespace Discord.API [JsonProperty("is_private")] public bool IsPrivate { get; set; } [JsonProperty("last_message_id")] - public ulong LastMessageId { get; set; } + public ulong? LastMessageId { get; set; } //GuildChannel [JsonProperty("guild_id")] diff --git a/src/Discord.Net/API/Common/ReadState.cs b/src/Discord.Net/API/Common/ReadState.cs index 6fa0c9b6e..e4177bedf 100644 --- a/src/Discord.Net/API/Common/ReadState.cs +++ b/src/Discord.Net/API/Common/ReadState.cs @@ -9,6 +9,6 @@ namespace Discord.API [JsonProperty("mention_count")] public int MentionCount { get; set; } [JsonProperty("last_message_id")] - public ulong LastMentionId { get; set; } + public ulong? LastMessageId { get; set; } } } From 7b68b4a860e021dc05b4716900af52e483a328a1 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 03:22:40 -0300 Subject: [PATCH 10/39] Simplified ToString, added debug strings --- src/Discord.Net/API/Optional.cs | 5 ++- .../Entities/Guilds/IntegrationAccount.cs | 8 +++-- .../Common/Entities/Guilds/VoiceRegion.cs | 7 ++-- .../Common/Entities/Invites/Invite.cs | 5 ++- .../Entities/Permissions/ChannelPermission.cs | 2 +- .../Permissions/ChannelPermissions.cs | 17 ++++----- .../Entities/Permissions/GuildPermission.cs | 2 +- .../Entities/Permissions/GuildPermissions.cs | 22 ++++++------ .../Permissions/OverwritePermissions.cs | 35 ++++++++++++------- .../Common/Entities/Users/Connection.cs | 5 ++- .../Rest/Entities/Channels/DMChannel.cs | 7 ++-- .../Rest/Entities/Channels/GuildChannel.cs | 3 ++ .../Rest/Entities/Channels/TextChannel.cs | 5 +-- .../Rest/Entities/Channels/VoiceChannel.cs | 5 +-- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 5 ++- .../Rest/Entities/Guilds/GuildEmbed.cs | 5 ++- .../Rest/Entities/Guilds/GuildIntegration.cs | 5 ++- .../Rest/Entities/Guilds/UserGuild.cs | 5 ++- src/Discord.Net/Rest/Entities/Message.cs | 5 ++- src/Discord.Net/Rest/Entities/Role.cs | 7 ++-- src/Discord.Net/Rest/Entities/Users/User.cs | 5 ++- .../WebSocket/Entities/Channels/DMChannel.cs | 7 ++-- .../Entities/Channels/GuildChannel.cs | 3 ++ .../Entities/Channels/TextChannel.cs | 5 +-- .../Entities/Channels/VoiceChannel.cs | 5 +-- .../WebSocket/Entities/Guilds/Guild.cs | 5 ++- .../Entities/Guilds/GuildIntegration.cs | 5 ++- src/Discord.Net/WebSocket/Entities/Message.cs | 5 ++- src/Discord.Net/WebSocket/Entities/Role.cs | 7 ++-- .../WebSocket/Entities/Users/User.cs | 5 ++- 30 files changed, 146 insertions(+), 66 deletions(-) diff --git a/src/Discord.Net/API/Optional.cs b/src/Discord.Net/API/Optional.cs index e76d170e5..b3af5c732 100644 --- a/src/Discord.Net/API/Optional.cs +++ b/src/Discord.Net/API/Optional.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics; namespace Discord.API { //Based on https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Nullable.cs + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Optional : IOptional { private readonly T _value; @@ -40,7 +42,8 @@ namespace Discord.API } public override int GetHashCode() => IsSpecified ? _value.GetHashCode() : 0; - public override string ToString() => IsSpecified ? _value.ToString() : ""; + public override string ToString() => IsSpecified ? _value?.ToString() : null; + private string DebuggerDisplay => IsSpecified ? _value.ToString() : ""; public static implicit operator Optional(T value) => new Optional(value); public static implicit operator T(Optional value) => value.Value; diff --git a/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs index 7b49d9de7..db0351bb1 100644 --- a/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs +++ b/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs @@ -1,5 +1,8 @@ -namespace Discord +using System.Diagnostics; + +namespace Discord { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct IntegrationAccount { /// @@ -8,6 +11,7 @@ /// public string Name { get; private set; } - public override string ToString() => Name ?? Id.ToString(); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; } } diff --git a/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs b/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs index 1c3ee7f20..f58da92aa 100644 --- a/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs +++ b/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs @@ -1,7 +1,9 @@ -using Model = Discord.API.VoiceRegion; +using System.Diagnostics; +using Model = Discord.API.VoiceRegion; namespace Discord.Rest { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class VoiceRegion : IVoiceRegion { /// @@ -27,6 +29,7 @@ namespace Discord.Rest SamplePort = model.SamplePort; } - public override string ToString() => $"{Name ?? Id.ToString()}"; + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsVip ? ", VIP" : "")}{(IsOptimal ? ", Optimal" : "")})"; } } diff --git a/src/Discord.Net/Common/Entities/Invites/Invite.cs b/src/Discord.Net/Common/Entities/Invites/Invite.cs index feae2119e..e18bc9079 100644 --- a/src/Discord.Net/Common/Entities/Invites/Invite.cs +++ b/src/Discord.Net/Common/Entities/Invites/Invite.cs @@ -1,8 +1,10 @@ -using System.Threading.Tasks; +using System.Diagnostics; +using System.Threading.Tasks; using Model = Discord.API.Invite; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Invite : IInvite { /// @@ -56,6 +58,7 @@ namespace Discord /// public override string ToString() => XkcdUrl ?? Url; + private string DebuggerDisplay => $"{XkcdUrl ?? Url} ({GuildName} / {ChannelName})"; string IEntity.Id => Code; } diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs index df8b88480..f3592b010 100644 --- a/src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs @@ -1,6 +1,6 @@ namespace Discord { - internal enum ChannelPermission : byte + public enum ChannelPermission : byte { //General CreateInstantInvite = 0, diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs index c7a430e4a..bc78704a8 100644 --- a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; namespace Discord { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct ChannelPermissions { private static ChannelPermissions _allDM { get; } = new ChannelPermissions(0b000100_000000_0011111111_0000011001); @@ -117,20 +119,19 @@ namespace Discord embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions); - /// - public override string ToString() + public List ToList() { - var perms = new List(); + var perms = new List(); ulong x = 1; for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((RawValue & x) != 0) - { - if (Enum.IsDefined(typeof(ChannelPermission), i)) - perms.Add($"{(ChannelPermission)i}"); - } + perms.Add((ChannelPermission)i); } - return string.Join(", ", perms); + return perms; } + /// + public override string ToString() => RawValue.ToString(); + private string DebuggerDisplay => $"{RawValue} ({string.Join(", ", ToList())})"; } } diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs b/src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs index 497b78726..3ba869cc5 100644 --- a/src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs @@ -1,6 +1,6 @@ namespace Discord { - internal enum GuildPermission : byte + public enum GuildPermission : byte { //General CreateInstantInvite = 0, diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs index 2ab0e6ddf..e5abb712f 100644 --- a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct GuildPermissions { /// Gets a blank GuildPermissions that grants no permissions. @@ -125,21 +128,20 @@ namespace Discord => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles); - - /// - public override string ToString() + + public List ToList() { - var perms = new List(); + var perms = new List(); ulong x = 1; for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((RawValue & x) != 0) - { - if (System.Enum.IsDefined(typeof(GuildPermission), i)) - perms.Add($"{(GuildPermission)i}"); - } + perms.Add((GuildPermission)i); } - return string.Join(", ", perms); + return perms; } + /// + public override string ToString() => RawValue.ToString(); + private string DebuggerDisplay => $"{RawValue} ({string.Join(", ", ToList())})"; } } diff --git a/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs index 5bfa3785a..7c448522e 100644 --- a/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct OverwritePermissions { /// Gets a blank OverwritePermissions that inherits all permissions. @@ -111,25 +113,32 @@ namespace Discord embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions); - /// - public override string ToString() + public List ToAllowList() { - var perms = new List(); + var perms = new List(); ulong x = 1; for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) { if ((AllowValue & x) != 0) - { - if (Enum.IsDefined(typeof(GuildPermission), i)) - perms.Add($"+{(GuildPermission)i}"); - } - else if ((DenyValue & x) != 0) - { - if (Enum.IsDefined(typeof(GuildPermission), i)) - perms.Add($"-{(GuildPermission)i}"); - } + perms.Add((ChannelPermission)i); + } + return perms; + } + public List ToDenyList() + { + var perms = new List(); + ulong x = 1; + for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) + { + if ((DenyValue & x) != 0) + perms.Add((ChannelPermission)i); } - return string.Join(", ", perms); + return perms; } + /// + public override string ToString() => $"Allow {AllowValue}, Deny {DenyValue}"; + private string DebuggerDisplay => + $"Allow {AllowValue} ({string.Join(", ", ToAllowList())})\n" + + $"Deny {DenyValue} ({string.Join(", ", ToDenyList())})"; } } diff --git a/src/Discord.Net/Common/Entities/Users/Connection.cs b/src/Discord.Net/Common/Entities/Users/Connection.cs index fc4524e90..c6eb65f2f 100644 --- a/src/Discord.Net/Common/Entities/Users/Connection.cs +++ b/src/Discord.Net/Common/Entities/Users/Connection.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; +using System.Diagnostics; using Model = Discord.API.Connection; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Connection : IConnection { public string Id { get; } @@ -23,6 +25,7 @@ namespace Discord.Rest IntegrationIds = model.Integrations; } - public override string ToString() => $"{Name ?? Id.ToString()} ({Type})"; + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}, Type = {Type}{(IsRevoked ? ", Revoked" : "")})"; } } diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index 90603d24e..ccda2bbd9 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class DMChannel : IDMChannel { /// @@ -121,8 +123,9 @@ namespace Discord.Rest } /// - public override string ToString() => $"@{Recipient} [DM]"; - + public override string ToString() => '@' + Recipient.ToString(); + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; + IDMUser IDMChannel.Recipient => Recipient; IEnumerable IMessageChannel.CachedMessages => Array.Empty(); diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index 7a07679c8..6373bccc7 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -148,6 +148,9 @@ namespace Discord.Rest Update(model); } + /// + public override string ToString() => Name; + IGuild IGuildChannel.Guild => Guild; async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index 5b4eb0d82..9ab247c9a 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -1,6 +1,7 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -8,6 +9,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class TextChannel : GuildChannel, ITextChannel { /// @@ -103,8 +105,7 @@ namespace Discord.Rest await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } - /// - public override string ToString() => $"{base.ToString()} [Text]"; + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; IEnumerable IMessageChannel.CachedMessages => Array.Empty(); diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index 8c76f3290..032e5eea1 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -1,11 +1,13 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class VoiceChannel : GuildChannel, IVoiceChannel { /// @@ -35,7 +37,6 @@ namespace Discord.Rest public override Task GetUser(ulong id) { throw new NotSupportedException(); } public override Task> GetUsers() { throw new NotSupportedException(); } - /// - public override string ToString() => $"{base.ToString()} [Voice]"; + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; } } diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index 421157b7d..d9015dea7 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -8,10 +8,12 @@ using System.Threading.Tasks; using Model = Discord.API.Guild; using EmbedModel = Discord.API.GuildEmbed; using RoleModel = Discord.API.Role; +using System.Diagnostics; namespace Discord.Rest { /// Represents a Discord guild (called a server in the official client). + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Guild : IGuild { private ConcurrentDictionary _roles; @@ -332,7 +334,8 @@ namespace Discord.Rest } } - public override string ToString() => Name ?? Id.ToString(); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; IEnumerable IGuild.Emojis => Emojis; ulong IGuild.EveryoneRoleId => EveryoneRole.Id; diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs index f53c621af..d7f5a3831 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildEmbed.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics; using Model = Discord.API.GuildEmbed; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class GuildEmbed : IGuildEmbed { /// @@ -26,6 +28,7 @@ namespace Discord IsEnabled = model.Enabled; } - public override string ToString() => $"{Id} ({(IsEnabled ? "Enabled" : "Disabled")})"; + public override string ToString() => Id.ToString(); + private string DebuggerDisplay => $"{Id}{(IsEnabled ? " (Enabled)" : "")}"; } } diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs index 5b2a83a78..2d0152d01 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs @@ -1,10 +1,12 @@ using Discord.API.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Integration; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class GuildIntegration : IGuildIntegration { /// @@ -77,7 +79,8 @@ namespace Discord.Rest await Discord.BaseClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } - public override string ToString() => $"{Name ?? Id.ToString()} ({(IsEnabled ? "Enabled" : "Disabled")})"; + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; IGuild IGuildIntegration.Guild => Guild; IRole IGuildIntegration.Role => Role; diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs index cead4e794..367a37cdb 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; namespace Discord { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class UserGuild : IUserGuild { private string _iconId; @@ -48,6 +50,7 @@ namespace Discord await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); } - public override string ToString() => Name ?? Id.ToString(); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsOwner ? ", Owned" : "")})"; } } diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index 3e5dd5012..017074a9f 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -2,11 +2,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Message : IMessage { /// @@ -135,7 +137,8 @@ namespace Discord.Rest await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); } - public override string ToString() => $"{Author.ToString()}: {Text}"; + public override string ToString() => Text; + private string DebuggerDisplay => $"{Author}: {Text}"; IUser IMessage.Author => Author; IReadOnlyList IMessage.Attachments => Attachments; diff --git a/src/Discord.Net/Rest/Entities/Role.cs b/src/Discord.Net/Rest/Entities/Role.cs index b22f3f9ea..465c089e6 100644 --- a/src/Discord.Net/Rest/Entities/Role.cs +++ b/src/Discord.Net/Rest/Entities/Role.cs @@ -1,12 +1,14 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Role; namespace Discord.Rest { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Role : IRole, IMentionable { /// @@ -66,8 +68,9 @@ namespace Discord.Rest => await Discord.BaseClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// - public override string ToString() => Name ?? Id.ToString(); - + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + ulong IRole.GuildId => Guild.Id; async Task> IRole.GetUsers() diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index 622bb6517..c44e1a0a5 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -1,10 +1,12 @@ using Discord.API.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; namespace Discord.Rest { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public abstract class User : IUser { private string _avatarId; @@ -51,7 +53,8 @@ namespace Discord.Rest return new DMChannel(Discord, model); } - public override string ToString() => $"{Username ?? Id.ToString()}"; + public override string ToString() => $"{Username}#{Discriminator}"; + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; /// Game? IUser.CurrentGame => null; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index 14deb2c52..c1e1f3474 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class DMChannel : IDMChannel { private readonly MessageCache _messages; @@ -114,8 +116,9 @@ namespace Discord.WebSocket } /// - public override string ToString() => $"@{Recipient} [DM]"; - + public override string ToString() => '@' + Recipient.ToString(); + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; + IDMUser IDMChannel.Recipient => Recipient; IEnumerable IMessageChannel.CachedMessages => CachedMessages; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 8a364302a..beb11fc64 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -133,6 +133,9 @@ namespace Discord.WebSocket await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); } + /// + public override string ToString() => Name; + IGuild IGuildChannel.Guild => Guild; async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index b9176da28..16801a91a 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class TextChannel : GuildChannel, ITextChannel { private readonly MessageCache _messages; @@ -105,8 +107,7 @@ namespace Discord.WebSocket await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } - /// - public override string ToString() => $"{base.ToString()} [Text]"; + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; IEnumerable IMessageChannel.CachedMessages => CachedMessages; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index 40c7ee586..24865c401 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -1,12 +1,14 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class VoiceChannel : GuildChannel, IVoiceChannel { /// @@ -43,7 +45,6 @@ namespace Discord.WebSocket return null; } - /// - public override string ToString() => $"{base.ToString()} [Voice]"; + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; } } diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 519a4b869..c5dfb4b5b 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -8,10 +8,12 @@ using System.Threading.Tasks; using Model = Discord.API.Guild; using EmbedModel = Discord.API.GuildEmbed; using RoleModel = Discord.API.Role; +using System.Diagnostics; namespace Discord.WebSocket { /// Represents a Discord guild (called a server in the official client). + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Guild : IGuild { private ConcurrentDictionary _roles; @@ -325,7 +327,8 @@ namespace Discord.WebSocket } } - public override string ToString() => Name ?? Id.ToString(); + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; IEnumerable IGuild.Emojis => Emojis; ulong IGuild.EveryoneRoleId => EveryoneRole.Id; diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs index 14c8a4911..9d93ee37a 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs @@ -1,10 +1,12 @@ using Discord.API.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Integration; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class GuildIntegration : IGuildIntegration { /// @@ -75,7 +77,8 @@ namespace Discord.WebSocket await Discord.BaseClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } - public override string ToString() => $"{Name ?? Id.ToString()} ({(IsEnabled ? "Enabled" : "Disabled")})"; + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; IGuild IGuildIntegration.Guild => Guild; IRole IGuildIntegration.Role => Role; diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index a68146a44..b5e659862 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -2,11 +2,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Message : IMessage { /// @@ -133,7 +135,8 @@ namespace Discord.WebSocket await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); } - public override string ToString() => $"{Author.ToString()}: {Text}"; + public override string ToString() => Text; + private string DebuggerDisplay => $"{Author}: {Text}"; IUser IMessage.Author => Author; IReadOnlyList IMessage.Attachments => Attachments; diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs index 6844cd41f..04ecec344 100644 --- a/src/Discord.Net/WebSocket/Entities/Role.cs +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -1,12 +1,14 @@ using Discord.API.Rest; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Role; namespace Discord.WebSocket { + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Role : IRole, IMentionable { /// @@ -65,8 +67,9 @@ namespace Discord.WebSocket => await Discord.BaseClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// - public override string ToString() => Name ?? Id.ToString(); - + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; + ulong IRole.GuildId => Guild.Id; async Task> IRole.GetUsers() diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index 42615dc65..0eb985e15 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -1,10 +1,12 @@ using Discord.API.Rest; using System; +using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; namespace Discord.WebSocket { + [DebuggerDisplay("{DebuggerDisplay,nq}")] public abstract class User : IUser { private string _avatarId; @@ -51,7 +53,8 @@ namespace Discord.WebSocket return new DMChannel(Discord, model); } - public override string ToString() => $"{Username ?? Id.ToString()}"; + public override string ToString() => $"{Username}#{Discriminator}"; + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; /// Game? IUser.CurrentGame => null; From a59bfaaeff704aaecbfa1e3cc4496063e881ccf3 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 15:13:31 -0300 Subject: [PATCH 11/39] Several GetMessages fixes --- src/Discord.Net/API/DiscordRawClient.cs | 20 +++++++++++-------- src/Discord.Net/API/Rest/CreateGuildParams.cs | 3 +-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 64055db84..9be604b86 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -582,29 +582,28 @@ namespace Discord.API if (args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); int limit = args.Limit; - ulong? relativeId = args.RelativeMessageId; + ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; string relativeDir = args.RelativeDirection == Direction.After ? "after" : "before"; int runs = limit / DiscordConfig.MaxMessagesPerBatch; - int lastRunCount = limit - runs * DiscordConfig.MaxMessagesPerBatch; + int lastRunCount = limit - (runs - 1) * DiscordConfig.MaxMessagesPerBatch; var result = new API.Message[runs][]; int i = 0; for (; i < runs; i++) { + int runCount = i == (runs - 1) ? lastRunCount : DiscordConfig.MaxMessagesPerBatch; string endpoint; if (relativeId != null) - endpoint = $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; + endpoint = $"channels/{channelId}/messages?limit={runCount}&{relativeDir}={relativeId}"; else - endpoint = $"channels/{channelId}/messages?limit={limit}"; + endpoint = $"channels/{channelId}/messages?limit={runCount}"; var models = await Send("GET", endpoint).ConfigureAwait(false); //Was this an empty batch? if (models.Length == 0) break; - result[i] = models; - - limit = (i == runs - 1) ? lastRunCount : DiscordConfig.MaxMessagesPerBatch; + result[i] = models; relativeId = args.RelativeDirection == Direction.Before ? models[0].Id : models[models.Length - 1].Id; //Was this an incomplete (the last) batch? @@ -612,7 +611,12 @@ namespace Discord.API } if (i > 1) - return result.Take(i).SelectMany(x => x); + { + if (args.RelativeDirection == Direction.Before) + return result.Take(i).SelectMany(x => x); + else + return result.Take(i).Reverse().SelectMany(x => x); + } else if (i == 1) return result[0]; else diff --git a/src/Discord.Net/API/Rest/CreateGuildParams.cs b/src/Discord.Net/API/Rest/CreateGuildParams.cs index 6cf0ed149..09c294b5e 100644 --- a/src/Discord.Net/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net/API/Rest/CreateGuildParams.cs @@ -1,5 +1,4 @@ -using Discord.Net.Converters; -using Newtonsoft.Json; +using Newtonsoft.Json; using System.IO; namespace Discord.API.Rest From 189938beaf1ffec18ff1669cb5b68c6ef0c91a56 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 19:07:00 -0300 Subject: [PATCH 12/39] Fixed GetMessages with non-multiples of 100 --- src/Discord.Net/API/DiscordRawClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 9be604b86..9875f423e 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -585,7 +585,7 @@ namespace Discord.API ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; string relativeDir = args.RelativeDirection == Direction.After ? "after" : "before"; - int runs = limit / DiscordConfig.MaxMessagesPerBatch; + int runs = (limit + DiscordConfig.MaxMessagesPerBatch - 1) / DiscordConfig.MaxMessagesPerBatch; int lastRunCount = limit - (runs - 1) * DiscordConfig.MaxMessagesPerBatch; var result = new API.Message[runs][]; From 7898b74207dd96b61c8e50ed0616958014d7a2c9 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 19:25:57 -0300 Subject: [PATCH 13/39] Added content preconditions to CreateMessage and UploadFile --- src/Discord.Net/API/DiscordRawClient.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 9875f423e..b4f23d5d4 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -628,6 +628,9 @@ namespace Discord.API { if (args == null) throw new ArgumentNullException(nameof(args)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + if (string.IsNullOrEmpty(args.Content)) throw new ArgumentNullException(nameof(args.Content)); + if (args.Content.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); if (guildId != 0) return await Send("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId).ConfigureAwait(false); @@ -641,6 +644,12 @@ namespace Discord.API if (args == null) throw new ArgumentNullException(nameof(args)); //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + if (args.Content.IsSpecified) + { + if (string.IsNullOrEmpty(args.Content.Value)) throw new ArgumentNullException(nameof(args.Content)); + if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + } if (guildId != 0) return await Send("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GuildBucket.SendEditMessage, guildId).ConfigureAwait(false); From 300922d6237f6f0888f1580654f7de62b9275ad3 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 21:45:57 -0300 Subject: [PATCH 14/39] Minor rename --- src/Discord.Net/Discord.Net.csproj | 2 +- .../WebSocket/Entities/Channels/GuildChannel.cs | 2 +- .../{ChannelPermissionsCache.cs => PermissionsCache.cs} | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) rename src/Discord.Net/WebSocket/{ChannelPermissionsCache.cs => PermissionsCache.cs} (92%) diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index f5e9372e3..172157cbc 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -207,7 +207,7 @@ - + diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index beb11fc64..afd0b17d9 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket public abstract class GuildChannel : IGuildChannel { private ConcurrentDictionary _overwrites; - internal ChannelPermissionsCache _permissions; + internal PermissionsCache _permissions; /// public ulong Id { get; } diff --git a/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs b/src/Discord.Net/WebSocket/PermissionsCache.cs similarity index 92% rename from src/Discord.Net/WebSocket/ChannelPermissionsCache.cs rename to src/Discord.Net/WebSocket/PermissionsCache.cs index fcf5ff6a8..30f3a0b6e 100644 --- a/src/Discord.Net/WebSocket/ChannelPermissionsCache.cs +++ b/src/Discord.Net/WebSocket/PermissionsCache.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -17,14 +16,14 @@ namespace Discord.WebSocket } } - internal class ChannelPermissionsCache + internal class PermissionsCache { private readonly GuildChannel _channel; private readonly ConcurrentDictionary _users; public IEnumerable Members => _users.Select(x => x.Value); - public ChannelPermissionsCache(GuildChannel channel) + public PermissionsCache(GuildChannel channel) { _channel = channel; _users = new ConcurrentDictionary(1, (int)(_channel.Guild.UserCount * 1.05)); From bf1e77e968e6ce5d170ad0b70ea841c2c291f0ab Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 23:13:58 -0300 Subject: [PATCH 15/39] Added explicit DM functions to RawClient --- src/Discord.Net/API/DiscordRawClient.cs | 70 ++++++++++++++----- .../Rest/Entities/Channels/DMChannel.cs | 8 +-- src/Discord.Net/Rest/Entities/Message.cs | 8 ++- .../WebSocket/Entities/Channels/DMChannel.cs | 8 +-- src/Discord.Net/WebSocket/Entities/Message.cs | 8 ++- 5 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index b4f23d5d4..1de95e6a3 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -622,9 +622,16 @@ namespace Discord.API else return Array.Empty(); } - public Task CreateMessage(ulong channelId, CreateMessageParams args) - => CreateMessage(0, channelId, args); - public async Task CreateMessage(ulong guildId, ulong channelId, CreateMessageParams args) + public Task CreateMessage(ulong guildId, ulong channelId, CreateMessageParams args) + { + if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + return CreateMessageInternal(guildId, channelId, args); + } + public Task CreateDMMessage(ulong channelId, CreateMessageParams args) + { + return CreateMessageInternal(0, channelId, args); + } + public async Task CreateMessageInternal(ulong guildId, ulong channelId, CreateMessageParams args) { if (args == null) throw new ArgumentNullException(nameof(args)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); @@ -637,12 +644,18 @@ namespace Discord.API else return await Send("POST", $"channels/{channelId}/messages", args, GlobalBucket.DirectMessage).ConfigureAwait(false); } - public Task UploadFile(ulong channelId, Stream file, UploadFileParams args) - => UploadFile(0, channelId, file, args); - public async Task UploadFile(ulong guildId, ulong channelId, Stream file, UploadFileParams args) + public Task UploadFile(ulong guildId, ulong channelId, Stream file, UploadFileParams args) + { + if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + return UploadFileInternal(guildId, channelId, file, args); + } + public Task UploadDMFile(ulong channelId, Stream file, UploadFileParams args) + { + return UploadFileInternal(0, channelId, file, args); + } + private async Task UploadFileInternal(ulong guildId, ulong channelId, Stream file, UploadFileParams args) { if (args == null) throw new ArgumentNullException(nameof(args)); - //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); if (args.Content.IsSpecified) { @@ -654,11 +667,18 @@ namespace Discord.API if (guildId != 0) return await Send("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GuildBucket.SendEditMessage, guildId).ConfigureAwait(false); else - return await Send("POST", $"channels/{channelId}/messages", file, args.ToDictionary()).ConfigureAwait(false); + return await Send("POST", $"channels/{channelId}/messages", file, args.ToDictionary(), GlobalBucket.DirectMessage).ConfigureAwait(false); + } + public Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId) + { + if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + return DeleteInternalMessage(guildId, channelId, messageId); } - public Task DeleteMessage(ulong channelId, ulong messageId) - => DeleteMessage(0, channelId, messageId); - public async Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId) + public Task DeleteDMMessage(ulong channelId, ulong messageId) + { + return DeleteInternalMessage(0, channelId, messageId); + } + private async Task DeleteInternalMessage(ulong guildId, ulong channelId, ulong messageId) { //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); @@ -669,11 +689,17 @@ namespace Discord.API else await Send("DELETE", $"channels/{channelId}/messages/{messageId}").ConfigureAwait(false); } - public Task DeleteMessages(ulong channelId, DeleteMessagesParam args) - => DeleteMessages(0, channelId, args); - public async Task DeleteMessages(ulong guildId, ulong channelId, DeleteMessagesParam args) + public Task DeleteMessages(ulong guildId, ulong channelId, DeleteMessagesParam args) + { + if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + return DeleteMessagesInternal(guildId, channelId, args); + } + public Task DeleteDMMessages(ulong channelId, DeleteMessagesParam args) + { + return DeleteMessagesInternal(0, channelId, args); + } + private async Task DeleteMessagesInternal(ulong guildId, ulong channelId, DeleteMessagesParam args) { - //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); if (args == null) throw new ArgumentNullException(nameof(args)); if (args.MessageIds == null) throw new ArgumentNullException(nameof(args.MessageIds)); @@ -694,12 +720,18 @@ namespace Discord.API break; } } - public Task ModifyMessage(ulong channelId, ulong messageId, ModifyMessageParams args) - => ModifyMessage(0, channelId, messageId, args); - public async Task ModifyMessage(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args) + public Task ModifyMessage(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args) + { + if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + return ModifyMessageInternal(guildId, channelId, messageId, args); + } + public Task ModifyDMMessage(ulong channelId, ulong messageId, ModifyMessageParams args) + { + return ModifyMessageInternal(0, channelId, messageId, args); + } + private async Task ModifyMessageInternal(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args) { if (args == null) throw new ArgumentNullException(nameof(args)); - //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); if (messageId == 0) throw new ArgumentOutOfRangeException(nameof(messageId)); diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index ccda2bbd9..e7e25150b 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -75,7 +75,7 @@ namespace Discord.Rest public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateMessage(Id, args).ConfigureAwait(false); + var model = await Discord.BaseClient.CreateDMMessage(Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -85,7 +85,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Id, file, args).ConfigureAwait(false); + var model = await Discord.BaseClient.UploadDMFile(Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -93,14 +93,14 @@ namespace Discord.Rest public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Id, stream, args).ConfigureAwait(false); + var model = await Discord.BaseClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.BaseClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index 017074a9f..25f2f64d8 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -127,14 +127,18 @@ namespace Discord.Rest if (guildChannel != null) model = await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - model = await Discord.BaseClient.ModifyMessage(Channel.Id, Id, args).ConfigureAwait(false); + model = await Discord.BaseClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); Update(model); } /// public async Task Delete() { - await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); + var guildChannel = Channel as GuildChannel; + if (guildChannel != null) + await Discord.BaseClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + else + await Discord.BaseClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index c1e1f3474..142295310 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -75,7 +75,7 @@ namespace Discord.WebSocket public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateMessage(Id, args).ConfigureAwait(false); + var model = await Discord.BaseClient.CreateDMMessage(Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -85,7 +85,7 @@ namespace Discord.WebSocket using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Id, file, args).ConfigureAwait(false); + var model = await Discord.BaseClient.UploadDMFile(Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -93,14 +93,14 @@ namespace Discord.WebSocket public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Id, stream, args).ConfigureAwait(false); + var model = await Discord.BaseClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.BaseClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index b5e659862..e55160415 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -126,13 +126,17 @@ namespace Discord.WebSocket if (guildChannel != null) await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - await Discord.BaseClient.ModifyMessage(Channel.Id, Id, args).ConfigureAwait(false); + await Discord.BaseClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.BaseClient.DeleteMessage(Channel.Id, Id).ConfigureAwait(false); + var guildChannel = Channel as GuildChannel; + if (guildChannel != null) + await Discord.BaseClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + else + await Discord.BaseClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; From 140ea65a8ead59bc2eb7ca609cf237443ee8e5d5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 23:16:10 -0300 Subject: [PATCH 16/39] Fixed DeleteMessages when a single message is passed. --- src/Discord.Net/API/DiscordRawClient.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 1de95e6a3..88887a42a 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -672,15 +672,14 @@ namespace Discord.API public Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId) { if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - return DeleteInternalMessage(guildId, channelId, messageId); + return DeleteMessageInternal(guildId, channelId, messageId); } public Task DeleteDMMessage(ulong channelId, ulong messageId) { - return DeleteInternalMessage(0, channelId, messageId); + return DeleteMessageInternal(0, channelId, messageId); } - private async Task DeleteInternalMessage(ulong guildId, ulong channelId, ulong messageId) + private async Task DeleteMessageInternal(ulong guildId, ulong channelId, ulong messageId) { - //if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); if (messageId == 0) throw new ArgumentOutOfRangeException(nameof(messageId)); @@ -710,7 +709,7 @@ namespace Discord.API case 0: return; case 1: - await DeleteMessage(guildId, channelId, messageIds[0]).ConfigureAwait(false); + await DeleteMessageInternal(guildId, channelId, messageIds[0]).ConfigureAwait(false); break; default: if (guildId != 0) From b42bf9cfd578e325182a627d2951933a1e6778dc Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 23:24:42 -0300 Subject: [PATCH 17/39] Fixed Optional.GetValueOrDefault(defaultValue) --- src/Discord.Net/API/Optional.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net/API/Optional.cs b/src/Discord.Net/API/Optional.cs index b3af5c732..6baef029f 100644 --- a/src/Discord.Net/API/Optional.cs +++ b/src/Discord.Net/API/Optional.cs @@ -32,7 +32,7 @@ namespace Discord.API } public T GetValueOrDefault() => _value; - public T GetValueOrDefault(T defaultValue) => IsSpecified ? _value : default(T); + public T GetValueOrDefault(T defaultValue) => IsSpecified ? _value : defaultValue; public override bool Equals(object other) { From 4714dd78e555e3e3d9e3b8363a49578b894aaa93 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 15 May 2016 23:25:22 -0300 Subject: [PATCH 18/39] Fixed GetGuildMembers for servers larger than 1000 members. --- src/Discord.Net/API/DiscordRawClient.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 88887a42a..55beb7f42 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -30,7 +30,7 @@ namespace Discord.API public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - internal DiscordRawClient(RestClientProvider restClientProvider) + public DiscordRawClient(RestClientProvider restClientProvider) { _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); _restClient.SetHeader("accept", "*/*"); @@ -474,11 +474,11 @@ namespace Discord.API { if (args == null) throw new ArgumentNullException(nameof(args)); if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); - if (args.Offset < 0) throw new ArgumentOutOfRangeException(nameof(args.Offset)); + if (args.Limit.IsSpecified && args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); + if (args.Offset.IsSpecified && args.Offset < 0) throw new ArgumentOutOfRangeException(nameof(args.Offset)); int limit = args.Limit.GetValueOrDefault(int.MaxValue); - int offset = args.Offset; + int offset = args.Offset.GetValueOrDefault(0); List result; if (args.Limit.IsSpecified) @@ -498,6 +498,7 @@ namespace Discord.API result.Add(models); limit -= DiscordConfig.MaxUsersPerBatch; + offset += models.Length; //Was this an incomplete (the last) batch? if (models.Length != DiscordConfig.MaxUsersPerBatch) break; From 67c810b715173947e3b9cc7b9da4398e0f618d36 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 00:38:28 -0300 Subject: [PATCH 19/39] Cleaned up Preconditions, removed implicit Optional cast --- src/Discord.Net/API/DiscordRawClient.cs | 293 +++++++++--------- src/Discord.Net/API/Optional.cs | 2 +- .../API/Rest/ModifyGuildChannelsParams.cs | 2 +- .../API/Rest/ModifyGuildRolesParams.cs | 2 +- src/Discord.Net/Discord.Net.csproj | 1 + src/Discord.Net/Preconditions.cs | 257 +++++++++++++++ .../Rest/Entities/Users/GuildUser.cs | 6 +- .../WebSocket/Entities/Users/GuildUser.cs | 6 +- 8 files changed, 420 insertions(+), 149 deletions(-) create mode 100644 src/Discord.Net/Preconditions.cs diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 55beb7f42..0bee8d8b9 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -169,7 +169,7 @@ namespace Discord.API //Channels public async Task GetChannel(ulong channelId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); try { @@ -179,7 +179,8 @@ namespace Discord.API } public async Task GetChannel(ulong guildId, ulong channelId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); try { @@ -192,57 +193,57 @@ namespace Discord.API } public async Task> GetGuildChannels(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return await Send>("GET", $"guilds/{guildId}/channels").ConfigureAwait(false); } public async Task CreateGuildChannel(ulong guildId, CreateGuildChannelParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Bitrate <= 0) throw new ArgumentOutOfRangeException(nameof(args.Bitrate)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); return await Send("POST", $"guilds/{guildId}/channels", args).ConfigureAwait(false); } public async Task DeleteChannel(ulong channelId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); return await Send("DELETE", $"channels/{channelId}").ConfigureAwait(false); } public async Task ModifyGuildChannel(ulong channelId, ModifyGuildChannelParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); - if (args.Position < 0) throw new ArgumentOutOfRangeException(nameof(args.Position)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } public async Task ModifyGuildChannel(ulong channelId, ModifyTextChannelParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); - if (args.Position < 0) throw new ArgumentOutOfRangeException(nameof(args.Position)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } public async Task ModifyGuildChannel(ulong channelId, ModifyVoiceChannelParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args.Bitrate <= 0) throw new ArgumentOutOfRangeException(nameof(args.Bitrate)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); - if (args.Position < 0) throw new ArgumentOutOfRangeException(nameof(args.Position)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } public async Task ModifyGuildChannels(ulong guildId, IEnumerable args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); var channels = args.ToArray(); switch (channels.Length) @@ -261,7 +262,8 @@ namespace Discord.API //Channel Permissions public async Task ModifyChannelPermissions(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); + Preconditions.NotNull(args, nameof(args)); + await Send("PUT", $"channels/{channelId}/permissions/{targetId}", args).ConfigureAwait(false); } public async Task DeleteChannelPermission(ulong channelId, ulong targetId) @@ -272,7 +274,7 @@ namespace Discord.API //Guilds public async Task GetGuild(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); try { @@ -282,50 +284,50 @@ namespace Discord.API } public async Task CreateGuild(CreateGuildParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (string.IsNullOrEmpty(args.Name)) throw new ArgumentNullException(nameof(args.Name)); - if (string.IsNullOrEmpty(args.Region)) throw new ArgumentNullException(nameof(args.Region)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrWhitespace(args.Region, nameof(args.Region)); return await Send("POST", "guilds", args).ConfigureAwait(false); } public async Task DeleteGuild(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send("DELETE", $"guilds/{guildId}").ConfigureAwait(false); } public async Task LeaveGuild(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send("DELETE", $"users/@me/guilds/{guildId}").ConfigureAwait(false); } public async Task ModifyGuild(ulong guildId, ModifyGuildParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.AFKChannelId == 0) throw new ArgumentOutOfRangeException(nameof(args.AFKChannelId)); - if (args.AFKTimeout < 0) throw new ArgumentOutOfRangeException(nameof(args.AFKTimeout)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); - //if (args.OwnerId == 0) throw new ArgumentOutOfRangeException(nameof(args.OwnerId)); - //if (args.Region == "") throw new ArgumentOutOfRangeException(nameof(args.Region)); - if (args.VerificationLevel < 0) throw new ArgumentOutOfRangeException(nameof(args.VerificationLevel)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(args.AFKChannelId, 0, nameof(args.AFKChannelId)); + Preconditions.AtLeast(args.AFKTimeout, 0, nameof(args.AFKTimeout)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNull(args.Owner, nameof(args.Owner)); + Preconditions.NotNull(args.Region, nameof(args.Region)); + Preconditions.AtLeast(args.VerificationLevel, 0, nameof(args.VerificationLevel)); return await Send("PATCH", $"guilds/{guildId}", args).ConfigureAwait(false); } public async Task BeginGuildPrune(ulong guildId, GuildPruneParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Days < 0) throw new ArgumentOutOfRangeException(nameof(args.Days)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Days, 0, nameof(args.Days)); return await Send("POST", $"guilds/{guildId}/prune", args).ConfigureAwait(false); } public async Task GetGuildPruneCount(ulong guildId, GuildPruneParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Days < 0) throw new ArgumentOutOfRangeException(nameof(args.Days)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Days, 0, nameof(args.Days)); return await Send("GET", $"guilds/{guildId}/prune", args).ConfigureAwait(false); } @@ -333,23 +335,23 @@ namespace Discord.API //Guild Bans public async Task> GetGuildBans(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return await Send>("GET", $"guilds/{guildId}/bans").ConfigureAwait(false); } public async Task CreateGuildBan(ulong guildId, ulong userId, CreateGuildBanParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); - if (args.PruneDays < 0) throw new ArgumentOutOfRangeException(nameof(args.PruneDays)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.PruneDays, 0, nameof(args.PruneDays)); await Send("PUT", $"guilds/{guildId}/bans/{userId}", args).ConfigureAwait(false); } public async Task RemoveGuildBan(ulong guildId, ulong userId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); await Send("DELETE", $"guilds/{guildId}/bans/{userId}").ConfigureAwait(false); } @@ -357,7 +359,7 @@ namespace Discord.API //Guild Embeds public async Task GetGuildEmbed(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); try { @@ -367,8 +369,8 @@ namespace Discord.API } public async Task ModifyGuildEmbed(ulong guildId, ModifyGuildEmbedParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send("PATCH", $"guilds/{guildId}/embed", args).ConfigureAwait(false); } @@ -376,39 +378,39 @@ namespace Discord.API //Guild Integrations public async Task> GetGuildIntegrations(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send>("GET", $"guilds/{guildId}/integrations").ConfigureAwait(false); } public async Task CreateGuildIntegration(ulong guildId, CreateGuildIntegrationParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Id == 0) throw new ArgumentOutOfRangeException(nameof(args.Id)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(args.Id, 0, nameof(args.Id)); return await Send("POST", $"guilds/{guildId}/integrations").ConfigureAwait(false); } public async Task DeleteGuildIntegration(ulong guildId, ulong integrationId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (integrationId == 0) throw new ArgumentOutOfRangeException(nameof(integrationId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); return await Send("DELETE", $"guilds/{guildId}/integrations/{integrationId}").ConfigureAwait(false); } public async Task ModifyGuildIntegration(ulong guildId, ulong integrationId, ModifyGuildIntegrationParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (integrationId == 0) throw new ArgumentOutOfRangeException(nameof(integrationId)); - if (args.ExpireBehavior < 0) throw new ArgumentOutOfRangeException(nameof(args.ExpireBehavior)); - if (args.ExpireGracePeriod < 0) throw new ArgumentOutOfRangeException(nameof(args.ExpireGracePeriod)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.ExpireBehavior, 0, nameof(args.ExpireBehavior)); + Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); return await Send("PATCH", $"guilds/{guildId}/integrations/{integrationId}", args).ConfigureAwait(false); } public async Task SyncGuildIntegration(ulong guildId, ulong integrationId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (integrationId == 0) throw new ArgumentOutOfRangeException(nameof(integrationId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); return await Send("POST", $"guilds/{guildId}/integrations/{integrationId}/sync").ConfigureAwait(false); } @@ -416,7 +418,7 @@ namespace Discord.API //Guild Invites public async Task GetInvite(string inviteIdOrXkcd) { - if (string.IsNullOrEmpty(inviteIdOrXkcd)) throw new ArgumentOutOfRangeException(nameof(inviteIdOrXkcd)); + Preconditions.NotNullOrEmpty(inviteIdOrXkcd, nameof(inviteIdOrXkcd)); try { @@ -426,34 +428,34 @@ namespace Discord.API } public async Task> GetGuildInvites(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send>("GET", $"guilds/{guildId}/invites").ConfigureAwait(false); } public async Task GetChannelInvites(ulong channelId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); return await Send("GET", $"channels/{channelId}/invites").ConfigureAwait(false); } public async Task CreateChannelInvite(ulong channelId, CreateChannelInviteParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args.MaxAge < 0) throw new ArgumentOutOfRangeException(nameof(args.MaxAge)); - if (args.MaxUses < 0) throw new ArgumentOutOfRangeException(nameof(args.MaxUses)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.MaxAge, 0, nameof(args.MaxAge)); + Preconditions.AtLeast(args.MaxUses, 0, nameof(args.MaxUses)); return await Send("POST", $"channels/{channelId}/invites", args).ConfigureAwait(false); } public async Task DeleteInvite(string inviteCode) { - if (string.IsNullOrEmpty(inviteCode)) throw new ArgumentOutOfRangeException(nameof(inviteCode)); + Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); return await Send("DELETE", $"invites/{inviteCode}").ConfigureAwait(false); } public async Task AcceptInvite(string inviteCode) { - if (string.IsNullOrEmpty(inviteCode)) throw new ArgumentOutOfRangeException(nameof(inviteCode)); + Preconditions.NotNullOrEmpty(inviteCode, nameof(inviteCode)); await Send("POST", $"invites/{inviteCode}").ConfigureAwait(false); } @@ -461,8 +463,8 @@ namespace Discord.API //Guild Members public async Task GetGuildMember(ulong guildId, ulong userId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); try { @@ -472,10 +474,10 @@ namespace Discord.API } public async Task> GetGuildMembers(ulong guildId, GetGuildMembersParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (args.Limit.IsSpecified && args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); - if (args.Offset.IsSpecified && args.Offset < 0) throw new ArgumentOutOfRangeException(nameof(args.Offset)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); + Preconditions.AtLeast(args.Offset, 0, nameof(args.Offset)); int limit = args.Limit.GetValueOrDefault(int.MaxValue); int offset = args.Offset.GetValueOrDefault(0); @@ -513,55 +515,55 @@ namespace Discord.API } public async Task RemoveGuildMember(ulong guildId, ulong userId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); await Send("DELETE", $"guilds/{guildId}/members/{userId}").ConfigureAwait(false); } public async Task ModifyGuildMember(ulong guildId, ulong userId, ModifyGuildMemberParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); - + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); + Preconditions.NotNull(args, nameof(args)); + await Send("PATCH", $"guilds/{guildId}/members/{userId}", args).ConfigureAwait(false); } //Guild Roles public async Task> GetGuildRoles(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send>("GET", $"guilds/{guildId}/roles").ConfigureAwait(false); } public async Task CreateGuildRole(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send("POST", $"guilds/{guildId}/roles").ConfigureAwait(false); } public async Task DeleteGuildRole(ulong guildId, ulong roleId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (roleId == 0) throw new ArgumentOutOfRangeException(nameof(roleId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(roleId, 0, nameof(roleId)); await Send("DELETE", $"guilds/{guildId}/roles/{roleId}").ConfigureAwait(false); } public async Task ModifyGuildRole(ulong guildId, ulong roleId, ModifyGuildRoleParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); - if (roleId == 0) throw new ArgumentOutOfRangeException(nameof(roleId)); - if (args.Color < 0) throw new ArgumentOutOfRangeException(nameof(args.Color)); - if (args.Name == "") throw new ArgumentOutOfRangeException(nameof(args.Name)); - if (args.Position < 0) throw new ArgumentOutOfRangeException(nameof(args.Position)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotEqual(roleId, 0, nameof(roleId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Color, 0, nameof(args.Color)); + Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); return await Send("PATCH", $"guilds/{guildId}/roles/{roleId}", args).ConfigureAwait(false); } public async Task> ModifyGuildRoles(ulong guildId, IEnumerable args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); var roles = args.ToArray(); switch (roles.Length) @@ -578,9 +580,9 @@ namespace Discord.API //Messages public async Task> GetChannelMessages(ulong channelId, GetChannelMessagesParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); int limit = args.Limit; ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; @@ -625,7 +627,8 @@ namespace Discord.API } public Task CreateMessage(ulong guildId, ulong channelId, CreateMessageParams args) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return CreateMessageInternal(guildId, channelId, args); } public Task CreateDMMessage(ulong channelId, CreateMessageParams args) @@ -634,11 +637,11 @@ namespace Discord.API } public async Task CreateMessageInternal(ulong guildId, ulong channelId, CreateMessageParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (string.IsNullOrEmpty(args.Content)) throw new ArgumentNullException(nameof(args.Content)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); if (guildId != 0) return await Send("POST", $"channels/{channelId}/messages", args, GuildBucket.SendEditMessage, guildId).ConfigureAwait(false); @@ -647,7 +650,8 @@ namespace Discord.API } public Task UploadFile(ulong guildId, ulong channelId, Stream file, UploadFileParams args) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return UploadFileInternal(guildId, channelId, file, args); } public Task UploadDMFile(ulong channelId, Stream file, UploadFileParams args) @@ -656,11 +660,11 @@ namespace Discord.API } private async Task UploadFileInternal(ulong guildId, ulong channelId, Stream file, UploadFileParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); if (args.Content.IsSpecified) { - if (string.IsNullOrEmpty(args.Content.Value)) throw new ArgumentNullException(nameof(args.Content)); + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); } @@ -672,7 +676,8 @@ namespace Discord.API } public Task DeleteMessage(ulong guildId, ulong channelId, ulong messageId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return DeleteMessageInternal(guildId, channelId, messageId); } public Task DeleteDMMessage(ulong channelId, ulong messageId) @@ -681,8 +686,8 @@ namespace Discord.API } private async Task DeleteMessageInternal(ulong guildId, ulong channelId, ulong messageId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (messageId == 0) throw new ArgumentOutOfRangeException(nameof(messageId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); if (guildId != 0) await Send("DELETE", $"channels/{channelId}/messages/{messageId}", GuildBucket.DeleteMessage, guildId).ConfigureAwait(false); @@ -691,7 +696,8 @@ namespace Discord.API } public Task DeleteMessages(ulong guildId, ulong channelId, DeleteMessagesParam args) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return DeleteMessagesInternal(guildId, channelId, args); } public Task DeleteDMMessages(ulong channelId, DeleteMessagesParam args) @@ -700,9 +706,9 @@ namespace Discord.API } private async Task DeleteMessagesInternal(ulong guildId, ulong channelId, DeleteMessagesParam args) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (args == null) throw new ArgumentNullException(nameof(args)); - if (args.MessageIds == null) throw new ArgumentNullException(nameof(args.MessageIds)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNull(args.MessageIds, nameof(args.MessageIds)); var messageIds = args.MessageIds.ToArray(); switch (messageIds.Length) @@ -722,7 +728,8 @@ namespace Discord.API } public Task ModifyMessage(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + return ModifyMessageInternal(guildId, channelId, messageId, args); } public Task ModifyDMMessage(ulong channelId, ulong messageId, ModifyMessageParams args) @@ -731,9 +738,15 @@ namespace Discord.API } private async Task ModifyMessageInternal(ulong guildId, ulong channelId, ulong messageId, ModifyMessageParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (messageId == 0) throw new ArgumentOutOfRangeException(nameof(messageId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNull(args, nameof(args)); + if (args.Content.IsSpecified) + { + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + } if (guildId != 0) return await Send("PATCH", $"channels/{channelId}/messages/{messageId}", args, GuildBucket.SendEditMessage, guildId).ConfigureAwait(false); @@ -742,14 +755,14 @@ namespace Discord.API } public async Task AckMessage(ulong channelId, ulong messageId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); - if (messageId == 0) throw new ArgumentOutOfRangeException(nameof(messageId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); await Send("POST", $"channels/{channelId}/messages/{messageId}/ack").ConfigureAwait(false); } public async Task TriggerTypingIndicator(ulong channelId) { - if (channelId == 0) throw new ArgumentOutOfRangeException(nameof(channelId)); + Preconditions.NotEqual(channelId, 0, nameof(channelId)); await Send("POST", $"channels/{channelId}/typing").ConfigureAwait(false); } @@ -757,7 +770,7 @@ namespace Discord.API //Users public async Task GetUser(ulong userId) { - if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); + Preconditions.NotEqual(userId, 0, nameof(userId)); try { @@ -767,7 +780,7 @@ namespace Discord.API } public async Task GetUser(string username, ushort discriminator) { - if (string.IsNullOrEmpty(username)) throw new ArgumentOutOfRangeException(nameof(username)); + Preconditions.NotNullOrEmpty(username, nameof(username)); try { @@ -778,8 +791,8 @@ namespace Discord.API } public async Task> QueryUsers(string query, int limit) { - if (string.IsNullOrEmpty(query)) throw new ArgumentOutOfRangeException(nameof(query)); - if (limit <= 0) throw new ArgumentOutOfRangeException(nameof(limit)); + Preconditions.NotNullOrEmpty(query, nameof(query)); + Preconditions.AtLeast(limit, 0, nameof(limit)); return await Send>("GET", $"users?q={Uri.EscapeDataString(query)}&limit={limit}").ConfigureAwait(false); } @@ -803,23 +816,23 @@ namespace Discord.API } public async Task ModifyCurrentUser(ModifyCurrentUserParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (args.Email == "") throw new ArgumentOutOfRangeException(nameof(args.Email)); - if (args.Username == "") throw new ArgumentOutOfRangeException(nameof(args.Username)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotNullOrEmpty(args.Email, nameof(args.Email)); + Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username)); return await Send("PATCH", "users/@me", args).ConfigureAwait(false); } public async Task ModifyCurrentUserNick(ulong guildId, ModifyCurrentUserNickParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (args.Nickname == "") throw new ArgumentOutOfRangeException(nameof(args.Nickname)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEmpty(args.Nickname, nameof(args.Nickname)); await Send("PATCH", $"guilds/{guildId}/members/@me/nick", args).ConfigureAwait(false); } public async Task CreateDMChannel(CreateDMChannelParams args) { - if (args == null) throw new ArgumentNullException(nameof(args)); - if (args.RecipientId == 0) throw new ArgumentOutOfRangeException(nameof(args.RecipientId)); + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(args.RecipientId, 0, nameof(args.RecipientId)); return await Send("POST", $"users/@me/channels", args).ConfigureAwait(false); } @@ -831,7 +844,7 @@ namespace Discord.API } public async Task> GetGuildVoiceRegions(ulong guildId) { - if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); return await Send>("GET", $"guilds/{guildId}/regions").ConfigureAwait(false); } diff --git a/src/Discord.Net/API/Optional.cs b/src/Discord.Net/API/Optional.cs index 6baef029f..0fe96071c 100644 --- a/src/Discord.Net/API/Optional.cs +++ b/src/Discord.Net/API/Optional.cs @@ -46,6 +46,6 @@ namespace Discord.API private string DebuggerDisplay => IsSpecified ? _value.ToString() : ""; public static implicit operator Optional(T value) => new Optional(value); - public static implicit operator T(Optional value) => value.Value; + public static explicit operator T(Optional value) => value.Value; } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs index 8444bb598..23d498f25 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs @@ -5,7 +5,7 @@ namespace Discord.API.Rest public class ModifyGuildChannelsParams { [JsonProperty("id")] - public Optional Id { get; set; } + public ulong Id { get; set; } [JsonProperty("position")] public Optional Position { get; set; } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs index 286c2463d..7002079d5 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class ModifyGuildRolesParams : ModifyGuildRoleParams { [JsonProperty("id")] - public Optional Id { get; set; } + public ulong Id { get; set; } } } diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 172157cbc..6a3f65685 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -107,6 +107,7 @@ + diff --git a/src/Discord.Net/Preconditions.cs b/src/Discord.Net/Preconditions.cs new file mode 100644 index 000000000..550f4beae --- /dev/null +++ b/src/Discord.Net/Preconditions.cs @@ -0,0 +1,257 @@ +using Discord.API; +using System; +using System.Runtime.CompilerServices; + +namespace Discord +{ + internal static class Preconditions + { + //Objects + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNull(T obj, string name) where T : class { if (obj == null) throw new ArgumentNullException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNull(Optional obj, string name) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } + + //Strings + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEmpty(string obj, string name) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEmpty(Optional obj, string name) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNullOrEmpty(string obj, string name) + { + if (obj == null) + throw new ArgumentNullException(name); + if (obj.Length == 0) + throw new ArgumentException("Argument cannot be empty.", name); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNullOrEmpty(Optional obj, string name) + { + if (obj.IsSpecified) + { + if (obj.Value == null) + throw new ArgumentNullException(name); + if (obj.Value.Length == 0) + throw new ArgumentException("Argument cannot be empty.", name); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNullOrWhitespace(string obj, string name) + { + if (obj == null) + throw new ArgumentNullException(name); + if (obj.Trim().Length == 0) + throw new ArgumentException("Argument cannot be blank.", name); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotNullOrWhitespace(Optional obj, string name) + { + if (obj.IsSpecified) + { + if (obj.Value == null) + throw new ArgumentNullException(name); + if (obj.Value.Trim().Length == 0) + throw new ArgumentException("Argument cannot be blank.", name); + } + } + + //Numerics + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(sbyte obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(byte obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(short obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(ushort obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(int obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(uint obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(long obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(ulong obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(sbyte? obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(byte? obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(short? obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(ushort? obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(int? obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(uint? obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(long? obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(ulong? obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(sbyte obj, sbyte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(byte obj, byte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(short obj, short value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(ushort obj, ushort value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(int obj, int value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(uint obj, uint value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(long obj, long value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(ulong obj, ulong value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(sbyte obj, sbyte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(byte obj, byte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(short obj, short value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(ushort obj, ushort value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(int obj, int value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(uint obj, uint value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(long obj, long value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(ulong obj, ulong value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(sbyte obj, sbyte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(byte obj, byte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(short obj, short value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(ushort obj, ushort value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(int obj, int value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(uint obj, uint value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(long obj, long value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(ulong obj, ulong value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(sbyte obj, sbyte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(byte obj, byte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(short obj, short value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(ushort obj, ushort value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(int obj, int value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(uint obj, uint value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(long obj, long value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(ulong obj, ulong value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + } +} diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index bc6dfa7a2..72c7d20a8 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -97,11 +97,11 @@ namespace Discord.Rest { await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) - IsDeaf = args.Deaf; + IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) - IsMute = args.Mute; + IsMute = args.Mute.Value; if (args.Nickname.IsSpecified) - Nickname = args.Nickname; + Nickname = args.Nickname.Value; if (args.Roles.IsSpecified) _roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); } diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 57b065a50..cafebe536 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -99,11 +99,11 @@ namespace Discord.WebSocket { await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) - IsDeaf = args.Deaf; + IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) - IsMute = args.Mute; + IsMute = args.Mute.Value; if (args.Nickname.IsSpecified) - Nickname = args.Nickname; + Nickname = args.Nickname.Value; if (args.Roles.IsSpecified) _roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); } From 4416b514bf839e259d00a659ddd5df62bbbb64d7 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 00:41:27 -0300 Subject: [PATCH 20/39] Fixed GuildUser not initializing --- src/Discord.Net/Rest/Entities/Users/GuildUser.cs | 2 ++ src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index 72c7d20a8..f3472d932 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -34,6 +34,8 @@ namespace Discord.Rest : base(model.User) { Guild = guild; + + Update(model); } internal void Update(Model model) { diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index cafebe536..8e8f25029 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -34,6 +34,8 @@ namespace Discord.WebSocket : base(model.User) { Guild = guild; + + Update(model); } internal void Update(Model model) { From f6058b2454066fd59ef91ef25eeefe0d169c2874 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 00:44:24 -0300 Subject: [PATCH 21/39] Use NotNullOrEmpty instead of NotNullOrWhitespace --- src/Discord.Net/API/DiscordRawClient.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index 0bee8d8b9..765078d3c 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -217,7 +217,7 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } @@ -226,7 +226,7 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } @@ -236,7 +236,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); - Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); return await Send("PATCH", $"channels/{channelId}", args).ConfigureAwait(false); } @@ -308,7 +308,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(args.AFKChannelId, 0, nameof(args.AFKChannelId)); Preconditions.AtLeast(args.AFKTimeout, 0, nameof(args.AFKTimeout)); - Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); Preconditions.NotNull(args.Owner, nameof(args.Owner)); Preconditions.NotNull(args.Region, nameof(args.Region)); Preconditions.AtLeast(args.VerificationLevel, 0, nameof(args.VerificationLevel)); @@ -555,7 +555,7 @@ namespace Discord.API Preconditions.NotEqual(roleId, 0, nameof(roleId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Color, 0, nameof(args.Color)); - Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); return await Send("PATCH", $"guilds/{guildId}/roles/{roleId}", args).ConfigureAwait(false); From 18f7e805efb1bae5cee32295a9954b64810c4594 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 01:20:29 -0300 Subject: [PATCH 22/39] Removed Rest's VoiceChannel.GetUser(s). Added IChannel.GetUser(limit, offset) --- .../Common/Entities/Channels/IChannel.cs | 2 ++ src/Discord.Net/Discord.Net.csproj | 2 +- .../Rest/Entities/Channels/DMChannel.cs | 2 ++ .../Rest/Entities/Channels/GuildChannel.cs | 23 ++++++++++--------- .../Rest/Entities/Channels/TextChannel.cs | 19 ++++++++++++--- .../Rest/Entities/Channels/VoiceChannel.cs | 5 ++-- .../WebSocket/Entities/Channels/DMChannel.cs | 2 ++ .../Entities/Channels/GuildChannel.cs | 2 ++ 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Discord.Net/Common/Entities/Channels/IChannel.cs b/src/Discord.Net/Common/Entities/Channels/IChannel.cs index 9540f4d26..74ab4a2f2 100644 --- a/src/Discord.Net/Common/Entities/Channels/IChannel.cs +++ b/src/Discord.Net/Common/Entities/Channels/IChannel.cs @@ -7,6 +7,8 @@ namespace Discord { /// Gets a collection of all users in this channel. Task> GetUsers(); + /// Gets a paginated collection of all users in this channel. + Task> GetUsers(int limit, int offset = 0); /// Gets a user in this channel with the provided id. Task GetUser(ulong id); } diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 6a3f65685..7a34de007 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -26,7 +26,7 @@ pdbonly true bin\Release\ - TRACE + TRACE;__DEMO__,__DEMO_EXPERIMENTAL__ prompt 4 diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index e7e25150b..8d8c5897b 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -131,6 +131,8 @@ namespace Discord.Rest async Task> IChannel.GetUsers() => await GetUsers().ConfigureAwait(false); + async Task> IChannel.GetUsers(int limit, int offset) + => (await GetUsers().ConfigureAwait(false)).Skip(offset).Take(limit); async Task IChannel.GetUser(ulong id) => await GetUser(id).ConfigureAwait(false); Task IMessageChannel.GetCachedMessage(ulong id) diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index 6373bccc7..1c3f64310 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -58,12 +58,7 @@ namespace Discord.Rest var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } - - /// Gets a user in this channel with the given id. - public abstract Task GetUser(ulong id); - /// Gets all users in this channel. - public abstract Task> GetUsers(); - + /// public OverwritePermissions? GetPermissionOverwrite(IUser user) { @@ -151,18 +146,24 @@ namespace Discord.Rest /// public override string ToString() => Name; + protected abstract Task GetUserInternal(ulong id); + protected abstract Task> GetUsersInternal(); + protected abstract Task> GetUsersInternal(int limit, int offset); + IGuild IGuildChannel.Guild => Guild; async Task IGuildChannel.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); async Task> IGuildChannel.GetInvites() => await GetInvites().ConfigureAwait(false); async Task> IGuildChannel.GetUsers() - => await GetUsers().ConfigureAwait(false); - async Task IGuildChannel.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); + => await GetUsersInternal().ConfigureAwait(false); async Task> IChannel.GetUsers() - => await GetUsers().ConfigureAwait(false); + => await GetUsersInternal().ConfigureAwait(false); + async Task> IChannel.GetUsers(int limit, int offset) + => await GetUsersInternal(limit, offset).ConfigureAwait(false); + async Task IGuildChannel.GetUser(ulong id) + => await GetUserInternal(id).ConfigureAwait(false); async Task IChannel.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); + => await GetUserInternal(id).ConfigureAwait(false); } } diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index 9ab247c9a..9fef515f2 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -39,19 +39,27 @@ namespace Discord.Rest Update(model); } - public override async Task GetUser(ulong id) + /// Gets a user in this channel with the given id. + public async Task GetUser(ulong id) { var user = await Guild.GetUser(id).ConfigureAwait(false); if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages)) return user; return null; } - public override async Task> GetUsers() + /// Gets all users in this channel. + public async Task> GetUsers() { var users = await Guild.GetUsers().ConfigureAwait(false); return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)); } - + /// Gets a paginated collection of users in this channel. + public async Task> GetUsers(int limit, int offset) + { + var users = await Guild.GetUsers(limit, offset).ConfigureAwait(false); + return users.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)); + } + /// public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { @@ -107,6 +115,11 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Text)"; + + protected override Task GetUserInternal(ulong id) => GetUser(id); + protected override Task> GetUsersInternal() => GetUsers(); + protected override Task> GetUsersInternal(int limit, int offset) => GetUsers(limit, offset); + IEnumerable IMessageChannel.CachedMessages => Array.Empty(); Task IMessageChannel.GetCachedMessage(ulong id) diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index 032e5eea1..eeab53ebf 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -34,8 +34,9 @@ namespace Discord.Rest Update(model); } - public override Task GetUser(ulong id) { throw new NotSupportedException(); } - public override Task> GetUsers() { throw new NotSupportedException(); } + protected override Task GetUserInternal(ulong id) { throw new NotSupportedException(); } + protected override Task> GetUsersInternal() { throw new NotSupportedException(); } + protected override Task> GetUsersInternal(int limit, int offset) { throw new NotSupportedException(); } private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; } diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index 142295310..89c2f9047 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -124,6 +124,8 @@ namespace Discord.WebSocket Task> IChannel.GetUsers() => Task.FromResult(Users); + Task> IChannel.GetUsers(int limit, int offset) + => Task.FromResult(Users.Skip(offset).Take(limit)); Task IChannel.GetUser(ulong id) => Task.FromResult(GetUser(id)); Task IMessageChannel.GetCachedMessage(ulong id) diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index afd0b17d9..6b3427a8c 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -147,6 +147,8 @@ namespace Discord.WebSocket => Task.FromResult(GetUser(id)); Task> IChannel.GetUsers() => Task.FromResult>(Users); + Task> IChannel.GetUsers(int limit, int offset) + => Task.FromResult>(Users.Skip(offset).Take(limit)); Task IChannel.GetUser(ulong id) => Task.FromResult(GetUser(id)); Task IUpdateable.Update() From 08413a399096068a14566d22abf87cdbf2fff6d4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 01:26:42 -0300 Subject: [PATCH 23/39] Removed Aggressive Inlines --- src/Discord.Net/Preconditions.cs | 113 ++----------------------------- 1 file changed, 4 insertions(+), 109 deletions(-) diff --git a/src/Discord.Net/Preconditions.cs b/src/Discord.Net/Preconditions.cs index 550f4beae..1bd8da7ac 100644 --- a/src/Discord.Net/Preconditions.cs +++ b/src/Discord.Net/Preconditions.cs @@ -1,23 +1,17 @@ using Discord.API; using System; -using System.Runtime.CompilerServices; namespace Discord { internal static class Preconditions { //Objects - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNull(T obj, string name) where T : class { if (obj == null) throw new ArgumentNullException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNull(Optional obj, string name) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } //Strings - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEmpty(string obj, string name) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEmpty(Optional obj, string name) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNullOrEmpty(string obj, string name) { if (obj == null) @@ -25,7 +19,6 @@ namespace Discord if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNullOrEmpty(Optional obj, string name) { if (obj.IsSpecified) @@ -36,7 +29,6 @@ namespace Discord throw new ArgumentException("Argument cannot be empty.", name); } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNullOrWhitespace(string obj, string name) { if (obj == null) @@ -44,7 +36,6 @@ namespace Discord if (obj.Trim().Length == 0) throw new ArgumentException("Argument cannot be blank.", name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotNullOrWhitespace(Optional obj, string name) { if (obj.IsSpecified) @@ -57,201 +48,105 @@ namespace Discord } //Numerics - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(sbyte obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(byte obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(short obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(ushort obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(int obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(uint obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(long obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(ulong obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(sbyte? obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(byte? obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(short? obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(ushort? obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(int? obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(uint? obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(long? obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(ulong? obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtLeast(sbyte obj, sbyte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(byte obj, byte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(short obj, short value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(ushort obj, ushort value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(int obj, int value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(uint obj, uint value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(long obj, long value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(ulong obj, ulong value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtLeast(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GreaterThan(sbyte obj, sbyte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(byte obj, byte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(short obj, short value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(ushort obj, ushort value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(int obj, int value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(uint obj, uint value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(long obj, long value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(ulong obj, ulong value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GreaterThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AtMost(sbyte obj, sbyte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(byte obj, byte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(short obj, short value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(ushort obj, ushort value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(int obj, int value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(uint obj, uint value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(long obj, long value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(ulong obj, ulong value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AtMost(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LessThan(sbyte obj, sbyte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(byte obj, byte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(short obj, short value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(ushort obj, ushort value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(int obj, int value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(uint obj, uint value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(long obj, long value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(ulong obj, ulong value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void LessThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } } } From 0f4e3819c5b3822342d92c1686b38ead131e9994 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 01:40:24 -0300 Subject: [PATCH 24/39] Added attachment count to Message.DebuggerDisplay --- src/Discord.Net/Rest/Entities/Message.cs | 2 +- src/Discord.Net/WebSocket/Entities/Message.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index 25f2f64d8..a037a0202 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -142,7 +142,7 @@ namespace Discord.Rest } public override string ToString() => Text; - private string DebuggerDisplay => $"{Author}: {Text}"; + private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; IUser IMessage.Author => Author; IReadOnlyList IMessage.Attachments => Attachments; diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index e55160415..8a22828fe 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -140,7 +140,7 @@ namespace Discord.WebSocket } public override string ToString() => Text; - private string DebuggerDisplay => $"{Author}: {Text}"; + private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; IUser IMessage.Author => Author; IReadOnlyList IMessage.Attachments => Attachments; From 2be3020689686dcb5ee3e9452b1833ab07104ca8 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 16 May 2016 02:04:49 -0300 Subject: [PATCH 25/39] Renamed DiscordRawClient to DiscordAPIClient --- ...iscordRawClient.cs => DiscordAPIClient.cs} | 4 +- .../Common/Entities/Invites/Invite.cs | 4 +- src/Discord.Net/Discord.Net.csproj | 2 +- src/Discord.Net/IDiscordClient.cs | 2 +- src/Discord.Net/Rest/DiscordClient.cs | 56 +++++++++---------- .../Rest/Entities/Channels/DMChannel.cs | 18 +++--- .../Rest/Entities/Channels/GuildChannel.cs | 18 +++--- .../Rest/Entities/Channels/TextChannel.cs | 16 +++--- .../Rest/Entities/Channels/VoiceChannel.cs | 2 +- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 48 ++++++++-------- .../Rest/Entities/Guilds/GuildIntegration.cs | 6 +- .../Rest/Entities/Guilds/UserGuild.cs | 4 +- src/Discord.Net/Rest/Entities/Message.cs | 8 +-- src/Discord.Net/Rest/Entities/Role.cs | 6 +- .../Rest/Entities/Users/GuildUser.cs | 8 +-- .../Rest/Entities/Users/SelfUser.cs | 4 +- src/Discord.Net/Rest/Entities/Users/User.cs | 2 +- src/Discord.Net/WebSocket/DiscordClient.cs | 2 +- .../WebSocket/Entities/Channels/DMChannel.cs | 12 ++-- .../Entities/Channels/GuildChannel.cs | 16 +++--- .../Entities/Channels/TextChannel.cs | 12 ++-- .../Entities/Channels/VoiceChannel.cs | 2 +- .../WebSocket/Entities/Guilds/Guild.cs | 46 +++++++-------- .../Entities/Guilds/GuildIntegration.cs | 6 +- src/Discord.Net/WebSocket/Entities/Message.cs | 8 +-- src/Discord.Net/WebSocket/Entities/Role.cs | 6 +- .../WebSocket/Entities/Users/GuildUser.cs | 6 +- .../WebSocket/Entities/Users/SelfUser.cs | 2 +- .../WebSocket/Entities/Users/User.cs | 2 +- src/Discord.Net/WebSocket/MessageCache.cs | 2 +- 30 files changed, 165 insertions(+), 165 deletions(-) rename src/Discord.Net/API/{DiscordRawClient.cs => DiscordAPIClient.cs} (99%) diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs similarity index 99% rename from src/Discord.Net/API/DiscordRawClient.cs rename to src/Discord.Net/API/DiscordAPIClient.cs index 765078d3c..bf0c1be96 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -17,7 +17,7 @@ using System.Threading.Tasks; namespace Discord.API { - public class DiscordRawClient + public class DiscordAPIClient { internal event EventHandler SentRequest; @@ -30,7 +30,7 @@ namespace Discord.API public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - public DiscordRawClient(RestClientProvider restClientProvider) + public DiscordAPIClient(RestClientProvider restClientProvider) { _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); _restClient.SetHeader("accept", "*/*"); diff --git a/src/Discord.Net/Common/Entities/Invites/Invite.cs b/src/Discord.Net/Common/Entities/Invites/Invite.cs index e18bc9079..88bd3b161 100644 --- a/src/Discord.Net/Common/Entities/Invites/Invite.cs +++ b/src/Discord.Net/Common/Entities/Invites/Invite.cs @@ -47,13 +47,13 @@ namespace Discord /// public async Task Accept() { - await Discord.BaseClient.AcceptInvite(Code).ConfigureAwait(false); + await Discord.APIClient.AcceptInvite(Code).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.BaseClient.DeleteInvite(Code).ConfigureAwait(false); + await Discord.APIClient.DeleteInvite(Code).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 7a34de007..45360f3ab 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -105,7 +105,7 @@ - + diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs index 068f56bff..e7a1cc31f 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net/IDiscordClient.cs @@ -10,7 +10,7 @@ namespace Discord public interface IDiscordClient { TokenType AuthTokenType { get; } - DiscordRawClient BaseClient { get; } + DiscordAPIClient APIClient { get; } IRestClient RestClient { get; } IRequestQueue RequestQueue { get; } diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index f57f9694c..8cd35dd23 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -24,11 +24,11 @@ namespace Discord.Rest private SelfUser _currentUser; public bool IsLoggedIn { get; private set; } - public API.DiscordRawClient BaseClient { get; private set; } + public API.DiscordAPIClient APIClient { get; private set; } - public TokenType AuthTokenType => BaseClient.AuthTokenType; - public IRestClient RestClient => BaseClient.RestClient; - public IRequestQueue RequestQueue => BaseClient.RequestQueue; + public TokenType AuthTokenType => APIClient.AuthTokenType; + public IRestClient RestClient => APIClient.RestClient; + public IRequestQueue RequestQueue => APIClient.RequestQueue; public DiscordClient(DiscordConfig config = null) { @@ -40,7 +40,7 @@ namespace Discord.Rest _connectionLock = new SemaphoreSlim(1, 1); _log = new LogManager(config.LogLevel); _userAgent = DiscordConfig.UserAgent; - BaseClient = new API.DiscordRawClient(_restClientProvider); + APIClient = new API.DiscordAPIClient(_restClientProvider); _log.Message += (s,e) => Log.Raise(this, e); } @@ -72,7 +72,7 @@ namespace Discord.Rest _cancelTokenSource = new CancellationTokenSource(); var args = new LoginParams { Email = email, Password = password }; - await BaseClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); + await APIClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); await CompleteLogin(false).ConfigureAwait(false); } catch { await LogoutInternal().ConfigureAwait(false); throw; } @@ -85,22 +85,22 @@ namespace Discord.Rest { _cancelTokenSource = new CancellationTokenSource(); - await BaseClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); + await APIClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); await CompleteLogin(validateToken).ConfigureAwait(false); } catch { await LogoutInternal().ConfigureAwait(false); throw; } } private async Task CompleteLogin(bool validateToken) { - BaseClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + APIClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); if (validateToken) { try { - await BaseClient.ValidateToken().ConfigureAwait(false); + await APIClient.ValidateToken().ConfigureAwait(false); } - catch { await BaseClient.Logout().ConfigureAwait(false); } + catch { await APIClient.Logout().ConfigureAwait(false); } } IsLoggedIn = true; @@ -127,7 +127,7 @@ namespace Discord.Rest catch { } } - await BaseClient.Logout().ConfigureAwait(false); + await APIClient.Logout().ConfigureAwait(false); _currentUser = null; if (wasLoggedIn) @@ -139,18 +139,18 @@ namespace Discord.Rest public async Task> GetConnections() { - var models = await BaseClient.GetCurrentUserConnections().ConfigureAwait(false); + var models = await APIClient.GetCurrentUserConnections().ConfigureAwait(false); return models.Select(x => new Connection(x)); } public async Task GetChannel(ulong id) { - var model = await BaseClient.GetChannel(id).ConfigureAwait(false); + var model = await APIClient.GetChannel(id).ConfigureAwait(false); if (model != null) { if (model.GuildId != null) { - var guildModel = await BaseClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); + var guildModel = await APIClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); if (guildModel != null) { var guild = new Guild(this, guildModel); @@ -164,13 +164,13 @@ namespace Discord.Rest } public async Task> GetDMChannels() { - var models = await BaseClient.GetCurrentUserDMs().ConfigureAwait(false); + var models = await APIClient.GetCurrentUserDMs().ConfigureAwait(false); return models.Select(x => new DMChannel(this, x)); } public async Task GetInvite(string inviteIdOrXkcd) { - var model = await BaseClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + var model = await APIClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); if (model != null) return new Invite(this, model); return null; @@ -178,41 +178,41 @@ namespace Discord.Rest public async Task GetGuild(ulong id) { - var model = await BaseClient.GetGuild(id).ConfigureAwait(false); + var model = await APIClient.GetGuild(id).ConfigureAwait(false); if (model != null) return new Guild(this, model); return null; } public async Task GetGuildEmbed(ulong id) { - var model = await BaseClient.GetGuildEmbed(id).ConfigureAwait(false); + var model = await APIClient.GetGuildEmbed(id).ConfigureAwait(false); if (model != null) return new GuildEmbed(model); return null; } public async Task> GetGuilds() { - var models = await BaseClient.GetCurrentUserGuilds().ConfigureAwait(false); + var models = await APIClient.GetCurrentUserGuilds().ConfigureAwait(false); return models.Select(x => new UserGuild(this, x)); } public async Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) { var args = new CreateGuildParams(); - var model = await BaseClient.CreateGuild(args).ConfigureAwait(false); + var model = await APIClient.CreateGuild(args).ConfigureAwait(false); return new Guild(this, model); } public async Task GetUser(ulong id) { - var model = await BaseClient.GetUser(id).ConfigureAwait(false); + var model = await APIClient.GetUser(id).ConfigureAwait(false); if (model != null) return new PublicUser(this, model); return null; } public async Task GetUser(string username, ushort discriminator) { - var model = await BaseClient.GetUser(username, discriminator).ConfigureAwait(false); + var model = await APIClient.GetUser(username, discriminator).ConfigureAwait(false); if (model != null) return new PublicUser(this, model); return null; @@ -222,7 +222,7 @@ namespace Discord.Rest var user = _currentUser; if (user == null) { - var model = await BaseClient.GetCurrentUser().ConfigureAwait(false); + var model = await APIClient.GetCurrentUser().ConfigureAwait(false); user = new SelfUser(this, model); _currentUser = user; } @@ -230,23 +230,23 @@ namespace Discord.Rest } public async Task> QueryUsers(string query, int limit) { - var models = await BaseClient.QueryUsers(query, limit).ConfigureAwait(false); + var models = await APIClient.QueryUsers(query, limit).ConfigureAwait(false); return models.Select(x => new PublicUser(this, x)); } public async Task> GetVoiceRegions() { - var models = await BaseClient.GetVoiceRegions().ConfigureAwait(false); + var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); return models.Select(x => new VoiceRegion(x)); } public async Task GetVoiceRegion(string id) { - var models = await BaseClient.GetVoiceRegions().ConfigureAwait(false); + var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault(); } public async Task GetOptimalVoiceRegion() { - var models = await BaseClient.GetVoiceRegions().ConfigureAwait(false); + var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); return models.Select(x => new VoiceRegion(x)).Where(x => x.IsOptimal).FirstOrDefault(); } @@ -261,7 +261,7 @@ namespace Discord.Rest } public void Dispose() => Dispose(true); - API.DiscordRawClient IDiscordClient.BaseClient => BaseClient; + API.DiscordAPIClient IDiscordClient.APIClient => APIClient; async Task IDiscordClient.GetChannel(ulong id) => await GetChannel(id).ConfigureAwait(false); diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index 8d8c5897b..b62bbc6f6 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -60,14 +60,14 @@ namespace Discord.Rest public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } /// public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } @@ -75,7 +75,7 @@ namespace Discord.Rest public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateDMMessage(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -85,7 +85,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadDMFile(Id, file, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -93,32 +93,32 @@ namespace Discord.Rest public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } /// public async Task Close() { - await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); } /// public async Task Update() { - var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); + var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index 1c3f64310..bdb92ffce 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -55,7 +55,7 @@ namespace Discord.Rest var args = new ModifyGuildChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } @@ -78,7 +78,7 @@ namespace Discord.Rest /// Downloads a collection of all invites to this channel. public async Task> GetInvites() { - var models = await Discord.BaseClient.GetChannelInvites(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } @@ -86,20 +86,20 @@ namespace Discord.Rest public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.BaseClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); } /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.BaseClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); } /// public async Task RemovePermissionOverwrite(IUser user) { - await Discord.BaseClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); Overwrite value; _overwrites.TryRemove(user.Id, out value); @@ -107,7 +107,7 @@ namespace Discord.Rest /// public async Task RemovePermissionOverwrite(IRole role) { - await Discord.BaseClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); Overwrite value; _overwrites.TryRemove(role.Id, out value); @@ -127,19 +127,19 @@ namespace Discord.Rest Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.BaseClient.CreateChannelInvite(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } /// public async Task Delete() { - await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); } /// public async Task Update() { - var model = await Discord.BaseClient.GetChannel(Id).ConfigureAwait(false); + var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index 9fef515f2..ee579ae0c 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -35,7 +35,7 @@ namespace Discord.Rest var args = new ModifyTextChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } @@ -64,14 +64,14 @@ namespace Discord.Rest public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } /// public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.BaseClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } @@ -79,7 +79,7 @@ namespace Discord.Rest public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -89,7 +89,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -97,20 +97,20 @@ namespace Discord.Rest public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } private string DebuggerDisplay => $"{Name} ({Id}, Text)"; diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index eeab53ebf..a2c6c8c49 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -30,7 +30,7 @@ namespace Discord.Rest var args = new ModifyVoiceChannelParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index d9015dea7..87d15108c 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -115,7 +115,7 @@ namespace Discord.Rest /// public async Task Update() { - var response = await Discord.BaseClient.GetGuild(Id).ConfigureAwait(false); + var response = await Discord.APIClient.GetGuild(Id).ConfigureAwait(false); Update(response); } /// @@ -125,7 +125,7 @@ namespace Discord.Rest var args = new ModifyGuildParams(); func(args); - var model = await Discord.BaseClient.ModifyGuild(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); Update(model); } /// @@ -135,35 +135,35 @@ namespace Discord.Rest var args = new ModifyGuildEmbedParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); Update(model); } /// public async Task ModifyChannels(IEnumerable args) { - await Discord.BaseClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) { - var models = await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); Update(models); } /// public async Task Leave() { - await Discord.BaseClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); } /// public async Task> GetBans() { - var models = await Discord.BaseClient.GetGuildBans(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); return models.Select(x => new PublicUser(Discord, x)); } /// @@ -175,20 +175,20 @@ namespace Discord.Rest { PruneDays = pruneDays }; - await Discord.BaseClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); + await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); } /// public Task RemoveBan(IUser user) => RemoveBan(user.Id); /// public async Task RemoveBan(ulong userId) { - await Discord.BaseClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); + await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); } /// Gets the channel in this guild with the provided id, or null if not found. public async Task GetChannel(ulong id) { - var model = await Discord.BaseClient.GetChannel(Id, id).ConfigureAwait(false); + var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); if (model != null) return ToChannel(model); return null; @@ -196,7 +196,7 @@ namespace Discord.Rest /// Gets a collection of all channels in this guild. public async Task> GetChannels() { - var models = await Discord.BaseClient.GetGuildChannels(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); return models.Select(x => ToChannel(x)); } /// Creates a new text channel. @@ -205,7 +205,7 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; - var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new TextChannel(this, model); } /// Creates a new voice channel. @@ -214,28 +214,28 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; - var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new VoiceChannel(this, model); } /// Gets a collection of all integrations attached to this guild. public async Task> GetIntegrations() { - var models = await Discord.BaseClient.GetGuildIntegrations(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); return models.Select(x => new GuildIntegration(this, x)); } /// Creates a new integration for this guild. public async Task CreateIntegration(ulong id, string type) { var args = new CreateGuildIntegrationParams { Id = id, Type = type }; - var model = await Discord.BaseClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); return new GuildIntegration(this, model); } /// Gets a collection of all invites to this guild. public async Task> GetInvites() { - var models = await Discord.BaseClient.GetGuildInvites(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. @@ -251,7 +251,7 @@ namespace Discord.Rest Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.BaseClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } @@ -269,7 +269,7 @@ namespace Discord.Rest { if (name == null) throw new ArgumentNullException(nameof(name)); - var model = await Discord.BaseClient.CreateGuildRole(Id).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); var role = new Role(this, model); await role.Modify(x => @@ -287,20 +287,20 @@ namespace Discord.Rest public async Task> GetUsers() { var args = new GetGuildMembersParams(); - var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets a paged collection of all users in this guild. public async Task> GetUsers(int limit, int offset) { var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; - var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets the user in this guild with the provided id, or null if not found. public async Task GetUser(ulong id) { - var model = await Discord.BaseClient.GetGuildMember(Id, id).ConfigureAwait(false); + var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); if (model != null) return new GuildUser(this, model); return null; @@ -316,9 +316,9 @@ namespace Discord.Rest var args = new GuildPruneParams() { Days = days }; GetGuildPruneCountResponse model; if (simulate) - model = await Discord.BaseClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); + model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); else - model = await Discord.BaseClient.BeginGuildPrune(Id, args).ConfigureAwait(false); + model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); return model.Pruned; } diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs index 2d0152d01..c8dd61ede 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs @@ -60,7 +60,7 @@ namespace Discord.Rest /// public async Task Delete() { - await Discord.BaseClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } /// public async Task Modify(Action func) @@ -69,14 +69,14 @@ namespace Discord.Rest var args = new ModifyGuildIntegrationParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); Update(model); } /// public async Task Sync() { - await Discord.BaseClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs index 367a37cdb..0679ccf21 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs @@ -42,12 +42,12 @@ namespace Discord /// public async Task Leave() { - await Discord.BaseClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index a037a0202..962c5af05 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -125,9 +125,9 @@ namespace Discord.Rest Model model; if (guildChannel != null) - model = await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + model = await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - model = await Discord.BaseClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); + model = await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); Update(model); } @@ -136,9 +136,9 @@ namespace Discord.Rest { var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.BaseClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); else - await Discord.BaseClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; diff --git a/src/Discord.Net/Rest/Entities/Role.cs b/src/Discord.Net/Rest/Entities/Role.cs index 465c089e6..cd6789c47 100644 --- a/src/Discord.Net/Rest/Entities/Role.cs +++ b/src/Discord.Net/Rest/Entities/Role.cs @@ -60,12 +60,12 @@ namespace Discord.Rest var args = new ModifyGuildRoleParams(); func(args); - var response = await Discord.BaseClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); + var response = await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); Update(response); } /// Deletes this message. public async Task Delete() - => await Discord.BaseClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); + => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// public override string ToString() => Name; @@ -76,7 +76,7 @@ namespace Discord.Rest async Task> IRole.GetUsers() { //A tad hacky, but it works - var models = await Discord.BaseClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); } } diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index f3472d932..3f5779965 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -55,7 +55,7 @@ namespace Discord.Rest public async Task Update() { - var model = await Discord.BaseClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); + var model = await Discord.APIClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); Update(model); } @@ -71,7 +71,7 @@ namespace Discord.Rest public async Task Kick() { - await Discord.BaseClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); } public ChannelPermissions GetPermissions(IGuildChannel channel) @@ -91,13 +91,13 @@ namespace Discord.Rest if (isCurrentUser && args.Nickname.IsSpecified) { var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; - await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); + await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) { - await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) diff --git a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs b/src/Discord.Net/Rest/Entities/Users/SelfUser.cs index 513acf0d2..503773382 100644 --- a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/SelfUser.cs @@ -30,7 +30,7 @@ namespace Discord.Rest /// public async Task Update() { - var model = await Discord.BaseClient.GetCurrentUser().ConfigureAwait(false); + var model = await Discord.APIClient.GetCurrentUser().ConfigureAwait(false); Update(model); } @@ -41,7 +41,7 @@ namespace Discord.Rest var args = new ModifyCurrentUserParams(); func(args); - var model = await Discord.BaseClient.ModifyCurrentUser(args).ConfigureAwait(false); + var model = await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); Update(model); } } diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index c44e1a0a5..0469b19d3 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -48,7 +48,7 @@ namespace Discord.Rest public async Task CreateDMChannel() { var args = new CreateDMChannelParams { RecipientId = Id }; - var model = await Discord.BaseClient.CreateDMChannel(args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); return new DMChannel(Discord, model); } diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 0af5cceb9..de342d2b2 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -27,7 +27,7 @@ namespace Discord.WebSocket } } - public DiscordRawClient BaseClient + public DiscordAPIClient APIClient { get { diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index 89c2f9047..abc5dd725 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -75,7 +75,7 @@ namespace Discord.WebSocket public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateDMMessage(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -85,7 +85,7 @@ namespace Discord.WebSocket using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadDMFile(Id, file, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -93,26 +93,26 @@ namespace Discord.WebSocket public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } /// public async Task Close() { - await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 6b3427a8c..5d8a6db68 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -57,7 +57,7 @@ namespace Discord.WebSocket var args = new ModifyGuildChannelParams(); func(args); - await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } /// Gets a user in this channel with the given id. @@ -82,7 +82,7 @@ namespace Discord.WebSocket /// Downloads a collection of all invites to this channel. public async Task> GetInvites() { - var models = await Discord.BaseClient.GetChannelInvites(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } @@ -90,23 +90,23 @@ namespace Discord.WebSocket public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.BaseClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); } /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.BaseClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); } /// public async Task RemovePermissionOverwrite(IUser user) { - await Discord.BaseClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); } /// public async Task RemovePermissionOverwrite(IRole role) { - await Discord.BaseClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); } /// Creates a new invite to this channel. @@ -123,14 +123,14 @@ namespace Discord.WebSocket Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.BaseClient.CreateChannelInvite(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } /// public async Task Delete() { - await Discord.BaseClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index 16801a91a..760fd6c84 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -42,7 +42,7 @@ namespace Discord.WebSocket var args = new ModifyTextChannelParams(); func(args); - await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } /// Gets the message from this channel's cache with the given id, or null if none was found. @@ -73,7 +73,7 @@ namespace Discord.WebSocket public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -83,7 +83,7 @@ namespace Discord.WebSocket using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -91,20 +91,20 @@ namespace Discord.WebSocket public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.BaseClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); + var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.BaseClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.BaseClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } private string DebuggerDisplay => $"{Name} ({Id}, Text)"; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index 24865c401..d9cc49500 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -34,7 +34,7 @@ namespace Discord.WebSocket var args = new ModifyVoiceChannelParams(); func(args); - await Discord.BaseClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } public override GuildUser GetUser(ulong id) diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index c5dfb4b5b..8ccb68fe3 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -121,7 +121,7 @@ namespace Discord.WebSocket var args = new ModifyGuildParams(); func(args); - await Discord.BaseClient.ModifyGuild(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); } /// public async Task ModifyEmbed(Action func) @@ -130,33 +130,33 @@ namespace Discord.WebSocket var args = new ModifyGuildEmbedParams(); func(args); - await Discord.BaseClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); } /// public async Task ModifyChannels(IEnumerable args) { - await Discord.BaseClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) { - await Discord.BaseClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); } /// public async Task Leave() { - await Discord.BaseClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.BaseClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); } /// public async Task> GetBans() { - var models = await Discord.BaseClient.GetGuildBans(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); return models.Select(x => new PublicUser(Discord, x)); } /// @@ -168,20 +168,20 @@ namespace Discord.WebSocket { PruneDays = pruneDays }; - await Discord.BaseClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); + await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); } /// public Task RemoveBan(IUser user) => RemoveBan(user.Id); /// public async Task RemoveBan(ulong userId) { - await Discord.BaseClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); + await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); } /// Gets the channel in this guild with the provided id, or null if not found. public async Task GetChannel(ulong id) { - var model = await Discord.BaseClient.GetChannel(Id, id).ConfigureAwait(false); + var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); if (model != null) return ToChannel(model); return null; @@ -189,7 +189,7 @@ namespace Discord.WebSocket /// Gets a collection of all channels in this guild. public async Task> GetChannels() { - var models = await Discord.BaseClient.GetGuildChannels(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); return models.Select(x => ToChannel(x)); } /// Creates a new text channel. @@ -198,7 +198,7 @@ namespace Discord.WebSocket if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; - var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new TextChannel(this, model); } /// Creates a new voice channel. @@ -207,28 +207,28 @@ namespace Discord.WebSocket if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; - var model = await Discord.BaseClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new VoiceChannel(this, model); } /// Gets a collection of all integrations attached to this guild. public async Task> GetIntegrations() { - var models = await Discord.BaseClient.GetGuildIntegrations(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); return models.Select(x => new GuildIntegration(this, x)); } /// Creates a new integration for this guild. public async Task CreateIntegration(ulong id, string type) { var args = new CreateGuildIntegrationParams { Id = id, Type = type }; - var model = await Discord.BaseClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); return new GuildIntegration(this, model); } /// Gets a collection of all invites to this guild. public async Task> GetInvites() { - var models = await Discord.BaseClient.GetGuildInvites(Id).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. @@ -244,7 +244,7 @@ namespace Discord.WebSocket Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.BaseClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } @@ -262,7 +262,7 @@ namespace Discord.WebSocket { if (name == null) throw new ArgumentNullException(nameof(name)); - var model = await Discord.BaseClient.CreateGuildRole(Id).ConfigureAwait(false); + var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); var role = new Role(this, model); await role.Modify(x => @@ -280,20 +280,20 @@ namespace Discord.WebSocket public async Task> GetUsers() { var args = new GetGuildMembersParams(); - var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets a paged collection of all users in this guild. public async Task> GetUsers(int limit, int offset) { var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; - var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets the user in this guild with the provided id, or null if not found. public async Task GetUser(ulong id) { - var model = await Discord.BaseClient.GetGuildMember(Id, id).ConfigureAwait(false); + var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); if (model != null) return new GuildUser(this, model); return null; @@ -309,9 +309,9 @@ namespace Discord.WebSocket var args = new GuildPruneParams() { Days = days }; GetGuildPruneCountResponse model; if (simulate) - model = await Discord.BaseClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); + model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); else - model = await Discord.BaseClient.BeginGuildPrune(Id, args).ConfigureAwait(false); + model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); return model.Pruned; } diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs index 9d93ee37a..fbb810e90 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs @@ -60,7 +60,7 @@ namespace Discord.WebSocket /// public async Task Delete() { - await Discord.BaseClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } /// public async Task Modify(Action func) @@ -69,12 +69,12 @@ namespace Discord.WebSocket var args = new ModifyGuildIntegrationParams(); func(args); - await Discord.BaseClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); } /// public async Task Sync() { - await Discord.BaseClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index 8a22828fe..dcd1dbf36 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -124,9 +124,9 @@ namespace Discord.WebSocket var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.BaseClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - await Discord.BaseClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); } /// @@ -134,9 +134,9 @@ namespace Discord.WebSocket { var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.BaseClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); else - await Discord.BaseClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); + await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs index 04ecec344..24af5f1d8 100644 --- a/src/Discord.Net/WebSocket/Entities/Role.cs +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -60,11 +60,11 @@ namespace Discord.WebSocket var args = new ModifyGuildRoleParams(); func(args); - await Discord.BaseClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); } /// Deletes this message. public async Task Delete() - => await Discord.BaseClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); + => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// public override string ToString() => Name; @@ -75,7 +75,7 @@ namespace Discord.WebSocket async Task> IRole.GetUsers() { //A tad hacky, but it works - var models = await Discord.BaseClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); + var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 8e8f25029..0dde4a8c5 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -69,7 +69,7 @@ namespace Discord.WebSocket public async Task Kick() { - await Discord.BaseClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); + await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); } public GuildPermissions GetGuildPermissions() @@ -93,13 +93,13 @@ namespace Discord.WebSocket if (isCurrentUser && args.Nickname.IsSpecified) { var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; - await Discord.BaseClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); + await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) { - await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) diff --git a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs index b852fbc15..9b97cf58e 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs @@ -34,7 +34,7 @@ namespace Discord.WebSocket var args = new ModifyCurrentUserParams(); func(args); - await Discord.BaseClient.ModifyCurrentUser(args).ConfigureAwait(false); + await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); } Task IUpdateable.Update() diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index 0eb985e15..f43465544 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -48,7 +48,7 @@ namespace Discord.WebSocket public async Task CreateDMChannel() { var args = new CreateDMChannelParams { RecipientId = Id }; - var model = await Discord.BaseClient.CreateDMChannel(args).ConfigureAwait(false); + var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); return new DMChannel(Discord, model); } diff --git a/src/Discord.Net/WebSocket/MessageCache.cs b/src/Discord.Net/WebSocket/MessageCache.cs index 1552dd2b1..d81fb3498 100644 --- a/src/Discord.Net/WebSocket/MessageCache.cs +++ b/src/Discord.Net/WebSocket/MessageCache.cs @@ -99,7 +99,7 @@ namespace Discord.WebSocket RelativeDirection = dir, RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id }; - var downloadedMessages = await _discord.BaseClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); + var downloadedMessages = await _discord.APIClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, x))).ToImmutableArray(); } } From b9d9191a3000acfee6c3ff2aa1c52321f6281f64 Mon Sep 17 00:00:00 2001 From: RogueException Date: Tue, 17 May 2016 05:29:44 -0300 Subject: [PATCH 26/39] Major cleanup, upgraded to RC2, ported several websocket features from 0.9. --- README.md | 14 +- global.json | 6 + src/Discord.Net/API/DiscordAPIClient.cs | 4 +- .../Common/Entities/Channels/IDMChannel.cs | 2 +- .../Common/Entities/Guilds/VoiceRegion.cs | 2 +- .../Common/Entities/Invites/Invite.cs | 4 +- .../Common/Entities/Messages/IMessage.cs | 2 +- .../Permissions/ChannelPermissions.cs | 44 ++- .../Entities/Permissions/GuildPermissions.cs | 4 + .../Entities/Permissions/Permissions.cs | 13 +- .../Common/Entities/Users/Connection.cs | 2 +- .../Common/Entities/Users/IDMUser.cs | 8 - .../Common/Entities/Users/IGuildUser.cs | 3 - .../Common/Entities/Users/IUser.cs | 1 + src/Discord.Net/Common/MentionUtils.cs | 6 +- src/Discord.Net/Discord.Net.csproj | 243 -------------- src/Discord.Net/Discord.Net.xproj | 19 ++ src/Discord.Net/IDiscordClient.cs | 3 +- .../Net/Converters/DiscordContractResolver.cs | 88 ++--- .../Net/Converters/OptionalConverter.cs | 2 +- src/Discord.Net/Net/Rest/DefaultRestClient.cs | 31 +- .../Net/Rest/RestClientProvider.cs | 4 +- .../Net/WebSockets/BinaryMessageEventArgs.cs | 11 + .../Net/WebSockets/DefaultWebsocketClient.cs | 180 ++++++++++ .../Net/WebSockets/IWebSocketClient.cs | 19 ++ .../Net/WebSockets/TextMessageEventArgs.cs | 11 + .../Net/WebSockets/WebSocketProvider.cs | 4 + src/Discord.Net/Properties/AssemblyInfo.cs | 23 +- src/Discord.Net/Rest/DiscordClient.cs | 74 ++--- .../Rest/Entities/Channels/DMChannel.cs | 30 +- .../Rest/Entities/Channels/GuildChannel.cs | 18 +- .../Rest/Entities/Channels/TextChannel.cs | 16 +- .../Rest/Entities/Channels/VoiceChannel.cs | 2 +- src/Discord.Net/Rest/Entities/Guilds/Guild.cs | 53 ++- .../Rest/Entities/Guilds/GuildIntegration.cs | 6 +- .../Rest/Entities/Guilds/UserGuild.cs | 4 +- src/Discord.Net/Rest/Entities/Message.cs | 59 ++-- src/Discord.Net/Rest/Entities/Role.cs | 8 +- src/Discord.Net/Rest/Entities/Users/DMUser.cs | 20 -- .../Rest/Entities/Users/GuildUser.cs | 18 +- .../Rest/Entities/Users/SelfUser.cs | 4 +- src/Discord.Net/Rest/Entities/Users/User.cs | 6 +- src/Discord.Net/WebSocket/DiscordClient.cs | 310 ++++++++++++++---- .../WebSocket/DiscordSocketConfig.cs | 39 +++ .../WebSocket/Entities/Channels/Channel.cs | 37 +++ .../WebSocket/Entities/Channels/DMChannel.cs | 58 ++-- .../Entities/Channels/GuildChannel.cs | 40 ++- .../Entities/Channels/TextChannel.cs | 18 +- .../Entities/Channels/VoiceChannel.cs | 2 +- .../WebSocket/Entities/Guilds/Guild.cs | 199 +++++------ .../Entities/Guilds/GuildIntegration.cs | 10 +- src/Discord.Net/WebSocket/Entities/Message.cs | 74 +++-- src/Discord.Net/WebSocket/Entities/Role.cs | 15 +- .../WebSocket/Entities/Users/DMUser.cs | 20 -- .../WebSocket/Entities/Users/GuildUser.cs | 83 +++-- .../WebSocket/Entities/Users/PublicUser.cs | 15 - .../WebSocket/Entities/Users/SelfUser.cs | 7 +- .../WebSocket/Entities/Users/User.cs | 22 +- .../WebSocket/Events/ChannelEventArgs.cs | 14 + .../Events/ChannelUpdatedEventArgs.cs | 14 + .../WebSocket/Events/CurrentUserEventArgs.cs | 14 + .../Events/CurrentUserUpdatedEventArgs.cs | 14 + .../WebSocket/Events/DisconnectedEventArgs.cs | 16 + .../WebSocket/Events/GuildEventArgs.cs | 14 + .../WebSocket/Events/GuildUpdatedEventArgs.cs | 14 + .../WebSocket/Events/MessageEventArgs.cs | 14 + .../Events/MessageUpdatedEventArgs.cs | 14 + .../WebSocket/Events/RoleEventArgs.cs | 14 + .../WebSocket/Events/RoleUpdatedEventArgs.cs | 14 + .../WebSocket/Events/TypingEventArgs.cs | 16 + .../WebSocket/Events/UserEventArgs.cs | 14 + .../WebSocket/Events/UserUpdatedEventArgs.cs | 14 + .../WebSocket/Events/VoiceChannelEventArgs.cs | 14 + src/Discord.Net/WebSocket/MessageCache.cs | 5 +- src/Discord.Net/project.json | 40 ++- 75 files changed, 1364 insertions(+), 915 deletions(-) create mode 100644 global.json delete mode 100644 src/Discord.Net/Common/Entities/Users/IDMUser.cs delete mode 100644 src/Discord.Net/Discord.Net.csproj create mode 100644 src/Discord.Net/Discord.Net.xproj create mode 100644 src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs create mode 100644 src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs create mode 100644 src/Discord.Net/Net/WebSockets/IWebSocketClient.cs create mode 100644 src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs create mode 100644 src/Discord.Net/Net/WebSockets/WebSocketProvider.cs delete mode 100644 src/Discord.Net/Rest/Entities/Users/DMUser.cs create mode 100644 src/Discord.Net/WebSocket/DiscordSocketConfig.cs create mode 100644 src/Discord.Net/WebSocket/Entities/Channels/Channel.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Users/DMUser.cs delete mode 100644 src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs create mode 100644 src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/GuildEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/MessageEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/RoleEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/TypingEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/UserEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs create mode 100644 src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs diff --git a/README.md b/README.md index 123764bd3..06f3ae426 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Discord.Net v1.0.0-dev +[![Build status](https://ci.appveyor.com/api/projects/status/p0n69xhqgmoobycf/branch/master?svg=true)](https://ci.appveyor.com/project/foxbot/discord-net/branch/master) + An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). -Check out the [documentation](https://discordnet.readthedocs.org/en/latest/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). +Check out the [documentation](http://rtd.discord.foxbot.me/en/docs-dev/index.html) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). -##### Warning: documentation is currently outdated. +##### Warning: Some of the documentation is outdated. It's current being rewritten. Until that's done, feel free to use my [DiscordBot](https://github.com/RogueException/DiscordBot) repo for reference. ### Installation @@ -16,9 +18,7 @@ You can download Discord.Net and its extensions from NuGet: ### Compiling In order to compile Discord.Net, you require at least the following: - [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs) -- Visual Studio 2015 Update 1 or higher (available through Visual Studio) -- [ASP.Net 5 RC1](https://get.asp.net) -- NuGet 3.3 (available through Visual Studio) +- [Visual Studio 2015 Update 2](https://www.visualstudio.com/en-us/news/vs2015-update2-vs.aspx) +- [Visual Studio .Net Core Plugin](https://www.microsoft.com/net/core#windows) +- NuGet 3.3+ (available through Visual Studio) -### Known Issues -- .Net Core support is incomplete on non-Windows systems diff --git a/global.json b/global.json new file mode 100644 index 000000000..7ee23dc6a --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-preview1-002702" + } +} diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index bf0c1be96..bde034256 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -17,7 +17,7 @@ using System.Threading.Tasks; namespace Discord.API { - public class DiscordAPIClient + public class DiscordApiClient { internal event EventHandler SentRequest; @@ -30,7 +30,7 @@ namespace Discord.API public IRestClient RestClient { get; private set; } public IRequestQueue RequestQueue { get; private set; } - public DiscordAPIClient(RestClientProvider restClientProvider) + public DiscordApiClient(RestClientProvider restClientProvider) { _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); _restClient.SetHeader("accept", "*/*"); diff --git a/src/Discord.Net/Common/Entities/Channels/IDMChannel.cs b/src/Discord.Net/Common/Entities/Channels/IDMChannel.cs index f6b53a91f..5038bf36c 100644 --- a/src/Discord.Net/Common/Entities/Channels/IDMChannel.cs +++ b/src/Discord.Net/Common/Entities/Channels/IDMChannel.cs @@ -5,7 +5,7 @@ namespace Discord public interface IDMChannel : IMessageChannel, IUpdateable { /// Gets the recipient of all messages in this channel. - IDMUser Recipient { get; } + IUser Recipient { get; } /// Closes this private channel, removing it from your channel list. Task Close(); diff --git a/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs b/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs index f58da92aa..126807202 100644 --- a/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs +++ b/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using Model = Discord.API.VoiceRegion; -namespace Discord.Rest +namespace Discord { [DebuggerDisplay("{DebuggerDisplay,nq}")] public class VoiceRegion : IVoiceRegion diff --git a/src/Discord.Net/Common/Entities/Invites/Invite.cs b/src/Discord.Net/Common/Entities/Invites/Invite.cs index 88bd3b161..97e1fc051 100644 --- a/src/Discord.Net/Common/Entities/Invites/Invite.cs +++ b/src/Discord.Net/Common/Entities/Invites/Invite.cs @@ -47,13 +47,13 @@ namespace Discord /// public async Task Accept() { - await Discord.APIClient.AcceptInvite(Code).ConfigureAwait(false); + await Discord.ApiClient.AcceptInvite(Code).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.APIClient.DeleteInvite(Code).ConfigureAwait(false); + await Discord.ApiClient.DeleteInvite(Code).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/Common/Entities/Messages/IMessage.cs b/src/Discord.Net/Common/Entities/Messages/IMessage.cs index 1e5eb3b3f..35107ccf2 100644 --- a/src/Discord.Net/Common/Entities/Messages/IMessage.cs +++ b/src/Discord.Net/Common/Entities/Messages/IMessage.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Discord.API.Rest; +using System.Collections.Generic; namespace Discord { diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs index bc78704a8..f5760f1a9 100644 --- a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs @@ -7,23 +7,37 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public struct ChannelPermissions { +#if CSHARP7 private static ChannelPermissions _allDM { get; } = new ChannelPermissions(0b000100_000000_0011111111_0000011001); private static ChannelPermissions _allText { get; } = new ChannelPermissions(0b000000_000000_0001110011_0000000000); private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(0b000100_111111_0000000000_0000011001); +#else + private static ChannelPermissions _allDM { get; } = new ChannelPermissions(Convert.ToUInt64("00010000000000111111110000011001", 2)); + private static ChannelPermissions _allText { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000000011100110000000000", 2)); + private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(Convert.ToUInt64("00010011111100000000000000011001", 2)); +#endif /// Gets a blank ChannelPermissions that grants no permissions. public static ChannelPermissions None { get; } = new ChannelPermissions(); /// Gets a ChannelPermissions that grants all permissions for a given channelType. public static ChannelPermissions All(IChannel channel) { +#if CSHARP7 switch (channel) { case ITextChannel _: return _allText; case IVoiceChannel _: return _allVoice; - case IGuildChannel _: return _allDM; + case IDMChannel _: return _allDM; default: throw new ArgumentException("Unknown channel type", nameof(channel)); } +#else + if (channel is ITextChannel) return _allText; + if (channel is IVoiceChannel) return _allVoice; + if (channel is IDMChannel) return _allDM; + + throw new ArgumentException("Unknown channel type", nameof(channel)); +#endif } /// Gets a packed value representing all the permissions in this ChannelPermissions. @@ -70,9 +84,9 @@ namespace Discord /// Creates a new ChannelPermissions with the provided packed value. public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } - private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, - bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) { @@ -100,25 +114,25 @@ namespace Discord } /// Creates a new ChannelPermissions with the provided permissions. - public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, - bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, - bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, + public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, + bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, + bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false) - : this(0, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, - embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, + : this(0, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, + embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions) { } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. - public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, - bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) - => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, - embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, + => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, manageMessages, + embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions); - + public List ToList() { var perms = new List(); diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs index e5abb712f..899cac80a 100644 --- a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs @@ -10,7 +10,11 @@ namespace Discord /// Gets a blank GuildPermissions that grants no permissions. public static readonly GuildPermissions None = new GuildPermissions(); /// Gets a GuildPermissions that grants all permissions. +#if CSHARP7 public static readonly GuildPermissions All = new GuildPermissions(0b000111_111111_0011111111_0000111111); +#else + public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("00011111111100111111110000111111", 2)); +#endif /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } diff --git a/src/Discord.Net/Common/Entities/Permissions/Permissions.cs b/src/Discord.Net/Common/Entities/Permissions/Permissions.cs index 235437fe2..3cd17e66e 100644 --- a/src/Discord.Net/Common/Entities/Permissions/Permissions.cs +++ b/src/Discord.Net/Common/Entities/Permissions/Permissions.cs @@ -130,6 +130,7 @@ namespace Discord if (overwrites.TryGetValue(user.Id, out entry)) resolvedPermissions = (resolvedPermissions & ~entry.Permissions.DenyValue) | entry.Permissions.AllowValue; +#if CSHARP7 switch (channel) { case ITextChannel _: @@ -140,10 +141,16 @@ namespace Discord if (!GetValue(resolvedPermissions, ChannelPermission.Connect)) resolvedPermissions = 0; //No read permission on a text channel removes all other permissions break; - default: - resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) - break; } +#else + var textChannel = channel as ITextChannel; + var voiceChannel = channel as IVoiceChannel; + if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages)) + resolvedPermissions = 0; //No read permission on a text channel removes all other permissions + else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect)) + resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions +#endif + resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) } return resolvedPermissions; diff --git a/src/Discord.Net/Common/Entities/Users/Connection.cs b/src/Discord.Net/Common/Entities/Users/Connection.cs index c6eb65f2f..10852820e 100644 --- a/src/Discord.Net/Common/Entities/Users/Connection.cs +++ b/src/Discord.Net/Common/Entities/Users/Connection.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using Model = Discord.API.Connection; -namespace Discord.Rest +namespace Discord { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Connection : IConnection diff --git a/src/Discord.Net/Common/Entities/Users/IDMUser.cs b/src/Discord.Net/Common/Entities/Users/IDMUser.cs deleted file mode 100644 index e8fdd1f19..000000000 --- a/src/Discord.Net/Common/Entities/Users/IDMUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Discord -{ - public interface IDMUser : IUser - { - /// Gets the private channel with this user. - IDMChannel Channel { get; } - } -} \ No newline at end of file diff --git a/src/Discord.Net/Common/Entities/Users/IGuildUser.cs b/src/Discord.Net/Common/Entities/Users/IGuildUser.cs index 5e038909e..f5d17688c 100644 --- a/src/Discord.Net/Common/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net/Common/Entities/Users/IGuildUser.cs @@ -29,9 +29,6 @@ namespace Discord /// Gets the channel-level permissions granted to this user for a given channel. ChannelPermissions GetPermissions(IGuildChannel channel); - /// Return true if this user has the provided role. - bool HasRole(IRole role); - /// Kicks this user from this guild. Task Kick(); /// Modifies this user's properties in this guild. diff --git a/src/Discord.Net/Common/Entities/Users/IUser.cs b/src/Discord.Net/Common/Entities/Users/IUser.cs index 9808c4a9f..c4754f3e3 100644 --- a/src/Discord.Net/Common/Entities/Users/IUser.cs +++ b/src/Discord.Net/Common/Entities/Users/IUser.cs @@ -17,6 +17,7 @@ namespace Discord /// Gets the username for this user. string Username { get; } + //TODO: CreateDMChannel is a candidate to move to IGuildUser, and User made a common class, depending on next friends list update /// Returns a private message channel to this user, creating one if it does not already exist. Task CreateDMChannel(); } diff --git a/src/Discord.Net/Common/MentionUtils.cs b/src/Discord.Net/Common/MentionUtils.cs index 7e4aba6d2..7d37ffc58 100644 --- a/src/Discord.Net/Common/MentionUtils.cs +++ b/src/Discord.Net/Common/MentionUtils.cs @@ -64,11 +64,11 @@ namespace Discord } /// Gets the ids of all users mentioned in a provided text. - public static IReadOnlyList GetUserMentions(string text) => GetMentions(text, _userRegex).ToArray(); + public static IImmutableList GetUserMentions(string text) => GetMentions(text, _userRegex).ToImmutableArray(); /// Gets the ids of all channels mentioned in a provided text. - public static IReadOnlyList GetChannelMentions(string text) => GetMentions(text, _channelRegex).ToArray(); + public static IImmutableList GetChannelMentions(string text) => GetMentions(text, _channelRegex).ToImmutableArray(); /// Gets the ids of all roles mentioned in a provided text. - public static IReadOnlyList GetRoleMentions(string text) => GetMentions(text, _roleRegex).ToArray(); + public static IImmutableList GetRoleMentions(string text) => GetMentions(text, _roleRegex).ToImmutableArray(); private static ImmutableArray.Builder GetMentions(string text, Regex regex) { var matches = regex.Matches(text); diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj deleted file mode 100644 index 45360f3ab..000000000 --- a/src/Discord.Net/Discord.Net.csproj +++ /dev/null @@ -1,243 +0,0 @@ - - - - - Debug - AnyCPU - {18F6FE23-73F6-4CA6-BBD9-F0139DC3EE90} - Library - Properties - Discord - Discord.Net - v4.6.1 - 512 - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;__DEMO__,__DEMO_EXPERIMENTAL__ - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE;__DEMO__,__DEMO_EXPERIMENTAL__ - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Discord.Net/Discord.Net.xproj b/src/Discord.Net/Discord.Net.xproj new file mode 100644 index 000000000..6759e09b4 --- /dev/null +++ b/src/Discord.Net/Discord.Net.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 91e9e7bd-75c9-4e98-84aa-2c271922e5c2 + Discord + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + \ No newline at end of file diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs index e7a1cc31f..3bdf82a43 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net/IDiscordClient.cs @@ -10,7 +10,7 @@ namespace Discord public interface IDiscordClient { TokenType AuthTokenType { get; } - DiscordAPIClient APIClient { get; } + DiscordApiClient ApiClient { get; } IRestClient RestClient { get; } IRequestQueue RequestQueue { get; } @@ -36,6 +36,5 @@ namespace Discord Task> GetVoiceRegions(); Task GetVoiceRegion(string id); - Task GetOptimalVoiceRegion(); } } diff --git a/src/Discord.Net/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net/Net/Converters/DiscordContractResolver.cs index a77d3cf28..678dc83cd 100644 --- a/src/Discord.Net/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net/Net/Converters/DiscordContractResolver.cs @@ -3,61 +3,73 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; -using System.Linq.Expressions; +using System.Linq; using System.Reflection; namespace Discord.Net.Converters { public class DiscordContractResolver : DefaultContractResolver - { + { + private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo(); + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); - var type = property.PropertyType; - JsonConverter converter = null; + var propInfo = member as PropertyInfo; - if (member.MemberType == MemberTypes.Property) + if (propInfo != null) { + JsonConverter converter = null; + var type = property.PropertyType; + var typeInfo = type.GetTypeInfo(); + //Primitives - if (type == typeof(ulong) && member.GetCustomAttribute() == null) - converter = UInt64Converter.Instance; - else if (type == typeof(ulong?) && member.GetCustomAttribute() == null) - converter = NullableUInt64Converter.Instance; - else if (typeof(IEnumerable).IsAssignableFrom(type) && member.GetCustomAttribute() == null) - converter = NullableUInt64Converter.Instance; + if (propInfo.GetCustomAttribute() == null) + { + if (type == typeof(ulong)) + converter = UInt64Converter.Instance; + else if (type == typeof(ulong?)) + converter = NullableUInt64Converter.Instance; + else if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEnumerable))) + converter = UInt64ArrayConverter.Instance; + } + if (converter == null) + { + //Enums + if (type == typeof(ChannelType)) + converter = ChannelTypeConverter.Instance; + else if (type == typeof(PermissionTarget)) + converter = PermissionTargetConverter.Instance; + else if (type == typeof(UserStatus)) + converter = UserStatusConverter.Instance; - //Enums - else if (type == typeof(ChannelType)) - converter = ChannelTypeConverter.Instance; - else if (type == typeof(PermissionTarget)) - converter = PermissionTargetConverter.Instance; - else if (type == typeof(UserStatus)) - converter = UserStatusConverter.Instance; + //Entities + if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) + converter = UInt64EntityConverter.Instance; + else if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) + converter = StringEntityConverter.Instance; - //Entities - else if (typeof(IEntity).IsAssignableFrom(type)) - converter = UInt64EntityConverter.Instance; - else if (typeof(IEntity).IsAssignableFrom(type)) - converter = StringEntityConverter.Instance; + //Special + else if (type == typeof(string) && propInfo.GetCustomAttribute() != null) + converter = ImageConverter.Instance; + else if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) + { + var lambda = (Func)propInfo.GetMethod.CreateDelegate(typeof(Func)); + /*var parentArg = Expression.Parameter(typeof(object)); + var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); + var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty); + var lambda = Expression.Lambda>(isSpecified, parentArg).Compile();*/ + property.ShouldSerialize = x => lambda(x); + converter = OptionalConverter.Instance; + } + } - //Special - else if (type == typeof(string) && member.GetCustomAttribute() != null) - converter = ImageConverter.Instance; - else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) + if (converter != null) { - var parentArg = Expression.Parameter(typeof(object)); - var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); - var isSpecified = Expression.Property(optional, OptionalConverter.IsSpecifiedProperty); - var lambda = Expression.Lambda>(isSpecified, parentArg).Compile(); - property.ShouldSerialize = x => lambda(x); - converter = OptionalConverter.Instance; + property.Converter = converter; + property.MemberConverter = converter; } } - if (converter != null) - { - property.Converter = converter; - property.MemberConverter = converter; - } return property; } diff --git a/src/Discord.Net/Net/Converters/OptionalConverter.cs b/src/Discord.Net/Net/Converters/OptionalConverter.cs index 44c670d76..aa1abe9e2 100644 --- a/src/Discord.Net/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net/Net/Converters/OptionalConverter.cs @@ -8,7 +8,7 @@ namespace Discord.Net.Converters public class OptionalConverter : JsonConverter { public static readonly OptionalConverter Instance = new OptionalConverter(); - internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetProperty(nameof(IOptional.IsSpecified)); + internal static readonly PropertyInfo IsSpecifiedProperty = typeof(IOptional).GetTypeInfo().GetDeclaredProperty(nameof(IOptional.IsSpecified)); public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net/Net/Rest/DefaultRestClient.cs index 86dee1150..07a29342b 100644 --- a/src/Discord.Net/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net/Net/Rest/DefaultRestClient.cs @@ -75,6 +75,7 @@ namespace Discord.Net.Rest { foreach (var p in multipartParams) { +#if CSHARP7 switch (p.Value) { case string value: @@ -92,6 +93,22 @@ namespace Discord.Net.Rest default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); } +#else + var stringValue = p.Value as string; + if (stringValue != null) { content.Add(new StringContent(stringValue), p.Key); continue; } + var byteArrayValue = p.Value as byte[]; + if (byteArrayValue != null) { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; } + var streamValue = p.Value as Stream; + if (streamValue != null) { content.Add(new StreamContent(streamValue), p.Key); continue; } + if (p.Value is MultipartFile) + { + var fileValue = (MultipartFile)p.Value; + content.Add(new StreamContent(fileValue.Stream), fileValue.Filename, p.Key); + continue; + } + + throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); +#endif } } restRequest.Content = content; @@ -101,21 +118,9 @@ namespace Discord.Net.Rest private async Task SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) { - int retryCount = 0; while (true) { - HttpResponseMessage response; - try - { - response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); - } - catch (WebException ex) - { - //The request was aborted: Could not create SSL/TLS secure channel. - if (ex.HResult == HR_SECURECHANNELFAILED && retryCount++ < 5) - continue; //Retrying seems to fix this somehow? - throw; - } + HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); int statusCode = (int)response.StatusCode; if (statusCode < 200 || statusCode >= 300) //2xx = Success diff --git a/src/Discord.Net/Net/Rest/RestClientProvider.cs b/src/Discord.Net/Net/Rest/RestClientProvider.cs index 341d18f02..51a7eb619 100644 --- a/src/Discord.Net/Net/Rest/RestClientProvider.cs +++ b/src/Discord.Net/Net/Rest/RestClientProvider.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace Discord.Net.Rest +namespace Discord.Net.Rest { public delegate IRestClient RestClientProvider(string baseUrl); } diff --git a/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs b/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs new file mode 100644 index 000000000..3fd4425fa --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class BinaryMessageEventArgs : EventArgs + { + public byte[] Data { get; } + + public BinaryMessageEventArgs(byte[] data) { } + } +} diff --git a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs new file mode 100644 index 000000000..23b2fd015 --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Concurrent; +using System.ComponentModel; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public class DefaultWebSocketClient : IWebSocketClient + { + public const int ReceiveChunkSize = 12 * 1024; //12KB + public const int SendChunkSize = 4 * 1024; //4KB + protected const int HR_TIMEOUT = -2147012894; + + public event EventHandler BinaryMessage = delegate { }; + public event EventHandler TextMessage = delegate { }; + + protected readonly ConcurrentQueue _sendQueue; + protected readonly ClientWebSocket _client; + protected Task _receiveTask, _sendTask; + protected CancellationTokenSource _cancelToken; + protected bool _isDisposed; + + public DefaultWebSocketClient() + { + _sendQueue = new ConcurrentQueue(); + + _client = new ClientWebSocket(); + _client.Options.Proxy = null; + _client.Options.KeepAliveInterval = TimeSpan.Zero; + } + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + _client.Dispose(); + _isDisposed = true; + } + } + public void Dispose() + { + Dispose(true); + } + + public async Task Connect(string host, CancellationToken cancelToken) + { + await Disconnect().ConfigureAwait(false); + + _cancelToken = new CancellationTokenSource(); + var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken.Token, cancelToken).Token; + + await _client.ConnectAsync(new Uri(host), combinedToken).ConfigureAwait(false); + _receiveTask = ReceiveAsync(combinedToken); + _sendTask = SendAsync(combinedToken); + } + public async Task Disconnect() + { + _cancelToken.Cancel(); + + string ignored; + while (_sendQueue.TryDequeue(out ignored)) { } + + _client.Abort(); + + var receiveTask = _receiveTask ?? Task.CompletedTask; + var sendTask = _sendTask ?? Task.CompletedTask; + await Task.WhenAll(receiveTask, sendTask).ConfigureAwait(false); + } + + public void SetHeader(string key, string value) + { + _client.Options.SetRequestHeader(key, value); + } + + public void QueueMessage(string message) + { + _sendQueue.Enqueue(message); + } + + //TODO: Check this code + private Task ReceiveAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + var buffer = new ArraySegment(new byte[ReceiveChunkSize]); + var stream = new MemoryStream(); + + try + { + while (!cancelToken.IsCancellationRequested) + { + WebSocketReceiveResult result = null; + do + { + if (cancelToken.IsCancellationRequested) return; + + try + { + result = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + throw new Exception($"Connection timed out."); + } + + if (result.MessageType == WebSocketMessageType.Close) + throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); + else + stream.Write(buffer.Array, 0, result.Count); + + } + while (result == null || !result.EndOfMessage); + + var array = stream.ToArray(); + if (result.MessageType == WebSocketMessageType.Binary) + BinaryMessage(this, new BinaryMessageEventArgs(array)); + else if (result.MessageType == WebSocketMessageType.Text) + { + string text = Encoding.UTF8.GetString(array, 0, array.Length); + TextMessage(this, new TextMessageEventArgs(text)); + } + + stream.Position = 0; + stream.SetLength(0); + } + } + catch (OperationCanceledException) { } + }); + } + + //TODO: Check this code + private Task SendAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + byte[] bytes = new byte[SendChunkSize]; + + try + { + while (!cancelToken.IsCancellationRequested) + { + string json; + while (_sendQueue.TryDequeue(out json)) + { + int byteCount = Encoding.UTF8.GetBytes(json, 0, json.Length, bytes, 0); + int frameCount = (int)Math.Ceiling((double)byteCount / SendChunkSize); + + int offset = 0; + for (int i = 0; i < frameCount; i++, offset += SendChunkSize) + { + bool isLast = i == (frameCount - 1); + + int count; + if (isLast) + count = byteCount - (i * SendChunkSize); + else + count = SendChunkSize; + + try + { + await _client.SendAsync(new ArraySegment(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + return; + } + } + } + await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + } +} diff --git a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs new file mode 100644 index 000000000..e6bbeb402 --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + //TODO: Add ETF + public interface IWebSocketClient + { + event EventHandler BinaryMessage; + event EventHandler TextMessage; + + void SetHeader(string key, string value); + + Task Connect(string host, CancellationToken cancelToken); + Task Disconnect(); + void QueueMessage(string message); + } +} diff --git a/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs b/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs new file mode 100644 index 000000000..e4e186044 --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class TextMessageEventArgs : EventArgs + { + public string Message { get; } + + public TextMessageEventArgs(string msg) { Message = msg; } + } +} diff --git a/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs b/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs new file mode 100644 index 000000000..ab41404b9 --- /dev/null +++ b/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs @@ -0,0 +1,4 @@ +namespace Discord.Net.WebSockets +{ + public delegate IWebSocketClient WebSocketProvider(string baseUrl); +} diff --git a/src/Discord.Net/Properties/AssemblyInfo.cs b/src/Discord.Net/Properties/AssemblyInfo.cs index 26e8c428e..7dcbdb315 100644 --- a/src/Discord.Net/Properties/AssemblyInfo.cs +++ b/src/Discord.Net/Properties/AssemblyInfo.cs @@ -1,16 +1,19 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyTitle("Discord.Net")] -[assembly: AssemblyProduct("Discord.Net")] -[assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")] -[assembly: AssemblyCompany("RogueException")] -[assembly: AssemblyCopyright("Copyright © RogueException 2016")] +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net.Core")] +[assembly: AssemblyTrademark("")] +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -[assembly: Guid("62ea817d-c945-4100-ba21-9dfb139d2868")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0-alpha1")] +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("91e9e7bd-75c9-4e98-84aa-2c271922e5c2")] diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index 8cd35dd23..90ab61ea1 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -10,25 +10,28 @@ using System.Threading.Tasks; namespace Discord.Rest { + //TODO: Docstrings + //TODO: Log Internal/External REST Rate Limits, 502s + //TODO: Log Logins/Logouts public sealed class DiscordClient : IDiscordClient, IDisposable { public event EventHandler Log; public event EventHandler LoggedIn, LoggedOut; + private readonly Logger _discordLogger, _restLogger; private readonly SemaphoreSlim _connectionLock; private readonly RestClientProvider _restClientProvider; private readonly LogManager _log; private CancellationTokenSource _cancelTokenSource; private bool _isDisposed; - private string _userAgent; private SelfUser _currentUser; public bool IsLoggedIn { get; private set; } - public API.DiscordAPIClient APIClient { get; private set; } + public API.DiscordApiClient ApiClient { get; private set; } - public TokenType AuthTokenType => APIClient.AuthTokenType; - public IRestClient RestClient => APIClient.RestClient; - public IRequestQueue RequestQueue => APIClient.RequestQueue; + public TokenType AuthTokenType => ApiClient.AuthTokenType; + public IRestClient RestClient => ApiClient.RestClient; + public IRequestQueue RequestQueue => ApiClient.RequestQueue; public DiscordClient(DiscordConfig config = null) { @@ -37,12 +40,14 @@ namespace Discord.Rest _restClientProvider = config.RestClientProvider; - _connectionLock = new SemaphoreSlim(1, 1); _log = new LogManager(config.LogLevel); - _userAgent = DiscordConfig.UserAgent; - APIClient = new API.DiscordAPIClient(_restClientProvider); + _log.Message += (s, e) => Log.Raise(this, e); + _discordLogger = _log.CreateLogger("Discord"); + _restLogger = _log.CreateLogger("Rest"); - _log.Message += (s,e) => Log.Raise(this, e); + _connectionLock = new SemaphoreSlim(1, 1); + ApiClient = new API.DiscordApiClient(_restClientProvider); + ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); } public async Task Login(string email, string password) @@ -72,7 +77,7 @@ namespace Discord.Rest _cancelTokenSource = new CancellationTokenSource(); var args = new LoginParams { Email = email, Password = password }; - await APIClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); + await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); await CompleteLogin(false).ConfigureAwait(false); } catch { await LogoutInternal().ConfigureAwait(false); throw; } @@ -85,22 +90,20 @@ namespace Discord.Rest { _cancelTokenSource = new CancellationTokenSource(); - await APIClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); + await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); await CompleteLogin(validateToken).ConfigureAwait(false); } catch { await LogoutInternal().ConfigureAwait(false); throw; } } private async Task CompleteLogin(bool validateToken) { - APIClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); - if (validateToken) { try { - await APIClient.ValidateToken().ConfigureAwait(false); + await ApiClient.ValidateToken().ConfigureAwait(false); } - catch { await APIClient.Logout().ConfigureAwait(false); } + catch { await ApiClient.Logout().ConfigureAwait(false); } } IsLoggedIn = true; @@ -127,7 +130,7 @@ namespace Discord.Rest catch { } } - await APIClient.Logout().ConfigureAwait(false); + await ApiClient.Logout().ConfigureAwait(false); _currentUser = null; if (wasLoggedIn) @@ -139,18 +142,18 @@ namespace Discord.Rest public async Task> GetConnections() { - var models = await APIClient.GetCurrentUserConnections().ConfigureAwait(false); + var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false); return models.Select(x => new Connection(x)); } public async Task GetChannel(ulong id) { - var model = await APIClient.GetChannel(id).ConfigureAwait(false); + var model = await ApiClient.GetChannel(id).ConfigureAwait(false); if (model != null) { if (model.GuildId != null) { - var guildModel = await APIClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); + var guildModel = await ApiClient.GetGuild(model.GuildId.Value).ConfigureAwait(false); if (guildModel != null) { var guild = new Guild(this, guildModel); @@ -164,13 +167,13 @@ namespace Discord.Rest } public async Task> GetDMChannels() { - var models = await APIClient.GetCurrentUserDMs().ConfigureAwait(false); + var models = await ApiClient.GetCurrentUserDMs().ConfigureAwait(false); return models.Select(x => new DMChannel(this, x)); } public async Task GetInvite(string inviteIdOrXkcd) { - var model = await APIClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); if (model != null) return new Invite(this, model); return null; @@ -178,41 +181,41 @@ namespace Discord.Rest public async Task GetGuild(ulong id) { - var model = await APIClient.GetGuild(id).ConfigureAwait(false); + var model = await ApiClient.GetGuild(id).ConfigureAwait(false); if (model != null) return new Guild(this, model); return null; } public async Task GetGuildEmbed(ulong id) { - var model = await APIClient.GetGuildEmbed(id).ConfigureAwait(false); + var model = await ApiClient.GetGuildEmbed(id).ConfigureAwait(false); if (model != null) return new GuildEmbed(model); return null; } public async Task> GetGuilds() { - var models = await APIClient.GetCurrentUserGuilds().ConfigureAwait(false); + var models = await ApiClient.GetCurrentUserGuilds().ConfigureAwait(false); return models.Select(x => new UserGuild(this, x)); } public async Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) { var args = new CreateGuildParams(); - var model = await APIClient.CreateGuild(args).ConfigureAwait(false); + var model = await ApiClient.CreateGuild(args).ConfigureAwait(false); return new Guild(this, model); } public async Task GetUser(ulong id) { - var model = await APIClient.GetUser(id).ConfigureAwait(false); + var model = await ApiClient.GetUser(id).ConfigureAwait(false); if (model != null) return new PublicUser(this, model); return null; } public async Task GetUser(string username, ushort discriminator) { - var model = await APIClient.GetUser(username, discriminator).ConfigureAwait(false); + var model = await ApiClient.GetUser(username, discriminator).ConfigureAwait(false); if (model != null) return new PublicUser(this, model); return null; @@ -222,7 +225,7 @@ namespace Discord.Rest var user = _currentUser; if (user == null) { - var model = await APIClient.GetCurrentUser().ConfigureAwait(false); + var model = await ApiClient.GetCurrentUser().ConfigureAwait(false); user = new SelfUser(this, model); _currentUser = user; } @@ -230,25 +233,20 @@ namespace Discord.Rest } public async Task> QueryUsers(string query, int limit) { - var models = await APIClient.QueryUsers(query, limit).ConfigureAwait(false); + var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false); return models.Select(x => new PublicUser(this, x)); } public async Task> GetVoiceRegions() { - var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); + var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false); return models.Select(x => new VoiceRegion(x)); } public async Task GetVoiceRegion(string id) { - var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); + var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false); return models.Select(x => new VoiceRegion(x)).Where(x => x.Id == id).FirstOrDefault(); } - public async Task GetOptimalVoiceRegion() - { - var models = await APIClient.GetVoiceRegions().ConfigureAwait(false); - return models.Select(x => new VoiceRegion(x)).Where(x => x.IsOptimal).FirstOrDefault(); - } void Dispose(bool disposing) { @@ -261,7 +259,7 @@ namespace Discord.Rest } public void Dispose() => Dispose(true); - API.DiscordAPIClient IDiscordClient.APIClient => APIClient; + API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; async Task IDiscordClient.GetChannel(ulong id) => await GetChannel(id).ConfigureAwait(false); @@ -289,7 +287,5 @@ namespace Discord.Rest => await GetVoiceRegions().ConfigureAwait(false); async Task IDiscordClient.GetVoiceRegion(string id) => await GetVoiceRegion(id).ConfigureAwait(false); - async Task IDiscordClient.GetOptimalVoiceRegion() - => await GetOptimalVoiceRegion().ConfigureAwait(false); } } diff --git a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs index b62bbc6f6..0efa29da3 100644 --- a/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/DMChannel.cs @@ -18,7 +18,7 @@ namespace Discord.Rest internal DiscordClient Discord { get; } /// - public DMUser Recipient { get; private set; } + public User Recipient { get; private set; } /// public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); @@ -33,13 +33,13 @@ namespace Discord.Rest private void Update(Model model) { if (Recipient == null) - Recipient = new DMUser(this, model.Recipient); + Recipient = new PublicUser(Discord, model.Recipient); else Recipient.Update(model.Recipient); } /// - public async Task GetUser(ulong id) + public async Task GetUser(ulong id) { var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); if (id == Recipient.Id) @@ -50,24 +50,24 @@ namespace Discord.Rest return null; } /// - public async Task> GetUsers() + public async Task> GetUsers() { var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); - return ImmutableArray.Create(currentUser, Recipient); + return ImmutableArray.Create(currentUser, Recipient); } /// public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } /// public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } @@ -75,7 +75,7 @@ namespace Discord.Rest public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateDMMessage(Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -85,7 +85,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); + var model = await Discord.ApiClient.UploadDMFile(Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -93,32 +93,32 @@ namespace Discord.Rest public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); + var model = await Discord.ApiClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.ApiClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } /// public async Task Close() { - await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); } /// public async Task Update() { - var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false); Update(model); } @@ -126,7 +126,7 @@ namespace Discord.Rest public override string ToString() => '@' + Recipient.ToString(); private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - IDMUser IDMChannel.Recipient => Recipient; + IUser IDMChannel.Recipient => Recipient; IEnumerable IMessageChannel.CachedMessages => Array.Empty(); async Task> IChannel.GetUsers() diff --git a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs index bdb92ffce..66e0abe19 100644 --- a/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/GuildChannel.cs @@ -55,7 +55,7 @@ namespace Discord.Rest var args = new ModifyGuildChannelParams(); func(args); - var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } @@ -78,7 +78,7 @@ namespace Discord.Rest /// Downloads a collection of all invites to this channel. public async Task> GetInvites() { - var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } @@ -86,20 +86,20 @@ namespace Discord.Rest public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); _overwrites[user.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = user.Id, TargetType = PermissionTarget.User }); } /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); _overwrites[role.Id] = new Overwrite(new API.Overwrite { Allow = perms.AllowValue, Deny = perms.DenyValue, TargetId = role.Id, TargetType = PermissionTarget.Role }); } /// public async Task RemovePermissionOverwrite(IUser user) { - await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); Overwrite value; _overwrites.TryRemove(user.Id, out value); @@ -107,7 +107,7 @@ namespace Discord.Rest /// public async Task RemovePermissionOverwrite(IRole role) { - await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); Overwrite value; _overwrites.TryRemove(role.Id, out value); @@ -127,19 +127,19 @@ namespace Discord.Rest Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } /// public async Task Delete() { - await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); } /// public async Task Update() { - var model = await Discord.APIClient.GetChannel(Id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetChannel(Id).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs index ee579ae0c..4c171bea2 100644 --- a/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/TextChannel.cs @@ -35,7 +35,7 @@ namespace Discord.Rest var args = new ModifyTextChannelParams(); func(args); - var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } @@ -64,14 +64,14 @@ namespace Discord.Rest public async Task> GetMessages(int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } /// public async Task> GetMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) { var args = new GetChannelMessagesParams { Limit = limit }; - var models = await Discord.APIClient.GetChannelMessages(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelMessages(Id, args).ConfigureAwait(false); return models.Select(x => new Message(this, x)); } @@ -79,7 +79,7 @@ namespace Discord.Rest public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); return new Message(this, model); } /// @@ -89,7 +89,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); + var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); return new Message(this, model); } } @@ -97,20 +97,20 @@ namespace Discord.Rest public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); + var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); return new Message(this, model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.ApiClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } private string DebuggerDisplay => $"{Name} ({Id}, Text)"; diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index a2c6c8c49..208e31e49 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -30,7 +30,7 @@ namespace Discord.Rest var args = new ModifyVoiceChannelParams(); func(args); - var model = await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); Update(model); } diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index 87d15108c..936a0d35c 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -115,7 +115,7 @@ namespace Discord.Rest /// public async Task Update() { - var response = await Discord.APIClient.GetGuild(Id).ConfigureAwait(false); + var response = await Discord.ApiClient.GetGuild(Id).ConfigureAwait(false); Update(response); } /// @@ -125,7 +125,7 @@ namespace Discord.Rest var args = new ModifyGuildParams(); func(args); - var model = await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false); Update(model); } /// @@ -135,35 +135,35 @@ namespace Discord.Rest var args = new ModifyGuildEmbedParams(); func(args); - var model = await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); Update(model); } /// public async Task ModifyChannels(IEnumerable args) { - await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) { - var models = await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); Update(models); } /// public async Task Leave() { - await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); } /// public async Task> GetBans() { - var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildBans(Id).ConfigureAwait(false); return models.Select(x => new PublicUser(Discord, x)); } /// @@ -171,24 +171,21 @@ namespace Discord.Rest /// public async Task AddBan(ulong userId, int pruneDays = 0) { - var args = new CreateGuildBanParams() - { - PruneDays = pruneDays - }; - await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); + var args = new CreateGuildBanParams() { PruneDays = pruneDays }; + await Discord.ApiClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); } /// public Task RemoveBan(IUser user) => RemoveBan(user.Id); /// public async Task RemoveBan(ulong userId) { - await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); + await Discord.ApiClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); } /// Gets the channel in this guild with the provided id, or null if not found. public async Task GetChannel(ulong id) { - var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetChannel(Id, id).ConfigureAwait(false); if (model != null) return ToChannel(model); return null; @@ -196,7 +193,7 @@ namespace Discord.Rest /// Gets a collection of all channels in this guild. public async Task> GetChannels() { - var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildChannels(Id).ConfigureAwait(false); return models.Select(x => ToChannel(x)); } /// Creates a new text channel. @@ -205,7 +202,7 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; - var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new TextChannel(this, model); } /// Creates a new voice channel. @@ -214,28 +211,28 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; - var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new VoiceChannel(this, model); } /// Gets a collection of all integrations attached to this guild. public async Task> GetIntegrations() { - var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildIntegrations(Id).ConfigureAwait(false); return models.Select(x => new GuildIntegration(this, x)); } /// Creates a new integration for this guild. public async Task CreateIntegration(ulong id, string type) { var args = new CreateGuildIntegrationParams { Id = id, Type = type }; - var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); return new GuildIntegration(this, model); } /// Gets a collection of all invites to this guild. public async Task> GetInvites() { - var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. @@ -251,7 +248,7 @@ namespace Discord.Rest Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } @@ -269,7 +266,7 @@ namespace Discord.Rest { if (name == null) throw new ArgumentNullException(nameof(name)); - var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildRole(Id).ConfigureAwait(false); var role = new Role(this, model); await role.Modify(x => @@ -287,20 +284,20 @@ namespace Discord.Rest public async Task> GetUsers() { var args = new GetGuildMembersParams(); - var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets a paged collection of all users in this guild. public async Task> GetUsers(int limit, int offset) { var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; - var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } /// Gets the user in this guild with the provided id, or null if not found. public async Task GetUser(ulong id) { - var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetGuildMember(Id, id).ConfigureAwait(false); if (model != null) return new GuildUser(this, model); return null; @@ -316,9 +313,9 @@ namespace Discord.Rest var args = new GuildPruneParams() { Days = days }; GetGuildPruneCountResponse model; if (simulate) - model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); else - model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false); return model.Pruned; } diff --git a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs index c8dd61ede..e368cc8d7 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/GuildIntegration.cs @@ -60,7 +60,7 @@ namespace Discord.Rest /// public async Task Delete() { - await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } /// public async Task Modify(Action func) @@ -69,14 +69,14 @@ namespace Discord.Rest var args = new ModifyGuildIntegrationParams(); func(args); - var model = await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); Update(model); } /// public async Task Sync() { - await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs index 0679ccf21..ae5c31da3 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/UserGuild.cs @@ -42,12 +42,12 @@ namespace Discord /// public async Task Leave() { - await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/Rest/Entities/Message.cs b/src/Discord.Net/Rest/Entities/Message.cs index 962c5af05..319394214 100644 --- a/src/Discord.Net/Rest/Entities/Message.cs +++ b/src/Discord.Net/Rest/Entities/Message.cs @@ -28,14 +28,14 @@ namespace Discord.Rest /// public IMessageChannel Channel { get; } /// - public User Author { get; } + public IUser Author { get; } /// public IReadOnlyList Attachments { get; private set; } /// public IReadOnlyList Embeds { get; private set; } /// - public IReadOnlyList MentionedUsers { get; private set; } + public IReadOnlyList MentionedUsers { get; private set; } /// public IReadOnlyList MentionedChannelIds { get; private set; } /// @@ -55,6 +55,10 @@ namespace Discord.Rest } private void Update(Model model) { + var guildChannel = Channel as GuildChannel; + var guild = guildChannel?.Guild; + var discord = Discord; + IsTTS = model.IsTextToSpeech; Timestamp = model.Timestamp; EditedTimestamp = model.EditedTimestamp; @@ -80,38 +84,32 @@ namespace Discord.Rest else Embeds = Array.Empty(); - if (model.Mentions.Length > 0) + if (guildChannel != null && model.Mentions.Length > 0) { - var discord = Discord; - var builder = ImmutableArray.CreateBuilder(model.Mentions.Length); + var mentions = new PublicUser[model.Mentions.Length]; for (int i = 0; i < model.Mentions.Length; i++) - builder.Add(new PublicUser(discord, model.Mentions[i])); - MentionedUsers = builder.ToArray(); + mentions[i] = new PublicUser(discord, model.Mentions[i]); + MentionedUsers = ImmutableArray.Create(mentions); } else MentionedUsers = Array.Empty(); - MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); - MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); - if (model.IsMentioningEveryone) + + if (guildChannel != null) { - ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; - if (guildId != null) - { - if (MentionedRoleIds.Count == 0) - MentionedRoleIds = ImmutableArray.Create(guildId.Value); - else - { - var builder = ImmutableArray.CreateBuilder(MentionedRoleIds.Count + 1); - builder.AddRange(MentionedRoleIds); - builder.Add(guildId.Value); - MentionedRoleIds = builder.ToImmutable(); - } - } + MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); + + var mentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); + if (model.IsMentioningEveryone) + mentionedRoleIds = mentionedRoleIds.Add(guildChannel.Guild.EveryoneRole.Id); + MentionedRoleIds = mentionedRoleIds; + } + else + { + MentionedChannelIds = Array.Empty(); + MentionedRoleIds = Array.Empty(); } Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); - - Author.Update(model.Author); } /// @@ -125,9 +123,9 @@ namespace Discord.Rest Model model; if (guildChannel != null) - model = await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - model = await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); Update(model); } @@ -136,18 +134,15 @@ namespace Discord.Rest { var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); else - await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; IUser IMessage.Author => Author; - IReadOnlyList IMessage.Attachments => Attachments; - IReadOnlyList IMessage.Embeds => Embeds; - IReadOnlyList IMessage.MentionedChannelIds => MentionedChannelIds; IReadOnlyList IMessage.MentionedUsers => MentionedUsers; } } diff --git a/src/Discord.Net/Rest/Entities/Role.cs b/src/Discord.Net/Rest/Entities/Role.cs index cd6789c47..20ed0940e 100644 --- a/src/Discord.Net/Rest/Entities/Role.cs +++ b/src/Discord.Net/Rest/Entities/Role.cs @@ -60,12 +60,12 @@ namespace Discord.Rest var args = new ModifyGuildRoleParams(); func(args); - var response = await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); + var response = await Discord.ApiClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); Update(response); } /// Deletes this message. public async Task Delete() - => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); + => await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// public override string ToString() => Name; @@ -75,8 +75,8 @@ namespace Discord.Rest async Task> IRole.GetUsers() { - //A tad hacky, but it works - var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); + //TODO: Rethink this, it isn't paginated or anything... + var models = await Discord.ApiClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); } } diff --git a/src/Discord.Net/Rest/Entities/Users/DMUser.cs b/src/Discord.Net/Rest/Entities/Users/DMUser.cs deleted file mode 100644 index 67bc534f3..000000000 --- a/src/Discord.Net/Rest/Entities/Users/DMUser.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Model = Discord.API.User; - -namespace Discord.Rest -{ - public class DMUser : User, IDMUser - { - /// - public DMChannel Channel { get; } - - internal override DiscordClient Discord => Channel.Discord; - - internal DMUser(DMChannel channel, Model model) - : base(model) - { - Channel = channel; - } - - IDMChannel IDMUser.Channel => Channel; - } -} diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index 3f5779965..c48aec4bf 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -55,23 +55,13 @@ namespace Discord.Rest public async Task Update() { - var model = await Discord.APIClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); + var model = await Discord.ApiClient.GetGuildMember(Guild.Id, Id).ConfigureAwait(false); Update(model); } - public bool HasRole(IRole role) - { - for (int i = 0; i < _roles.Length; i++) - { - if (_roles[i].Id == role.Id) - return true; - } - return false; - } - public async Task Kick() { - await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); } public ChannelPermissions GetPermissions(IGuildChannel channel) @@ -91,13 +81,13 @@ namespace Discord.Rest if (isCurrentUser && args.Nickname.IsSpecified) { var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; - await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); + await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) { - await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) diff --git a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs b/src/Discord.Net/Rest/Entities/Users/SelfUser.cs index 503773382..a821b369b 100644 --- a/src/Discord.Net/Rest/Entities/Users/SelfUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/SelfUser.cs @@ -30,7 +30,7 @@ namespace Discord.Rest /// public async Task Update() { - var model = await Discord.APIClient.GetCurrentUser().ConfigureAwait(false); + var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false); Update(model); } @@ -41,7 +41,7 @@ namespace Discord.Rest var args = new ModifyCurrentUserParams(); func(args); - var model = await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); + var model = await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false); Update(model); } } diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index 0469b19d3..26754fc18 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -45,10 +45,10 @@ namespace Discord.Rest Username = model.Username; } - public async Task CreateDMChannel() + protected virtual async Task CreateDMChannelInternal() { var args = new CreateDMChannelParams { RecipientId = Id }; - var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false); return new DMChannel(Discord, model); } @@ -63,6 +63,6 @@ namespace Discord.Rest /// async Task IUser.CreateDMChannel() - => await CreateDMChannel().ConfigureAwait(false); + => await CreateDMChannelInternal().ConfigureAwait(false); } } diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index de342d2b2..99fba0107 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -1,139 +1,305 @@ -using System; +using Discord.API.Rest; +using Discord.Logging; +using Discord.Net.Rest; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; +using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Discord.API; -using Discord.Net.Rest; namespace Discord.WebSocket { - public class DiscordClient : IDiscordClient + //TODO: Docstrings + //TODO: Log Logins/Logouts + public sealed class DiscordClient : IDiscordClient, IDisposable { - internal int MessageCacheSize { get; } = 100; + public event EventHandler Log; + public event EventHandler LoggedIn, LoggedOut; + public event EventHandler Connected, Disconnected; + public event EventHandler VoiceConnected, VoiceDisconnected; + + public event EventHandler ChannelCreated, ChannelDestroyed; + public event EventHandler ChannelUpdated; + public event EventHandler MessageReceived, MessageDeleted; + public event EventHandler MessageUpdated; + public event EventHandler RoleCreated, RoleDeleted; + public event EventHandler RoleUpdated; + public event EventHandler JoinedGuild, LeftGuild; + public event EventHandler GuildAvailable, GuildUnavailable; + public event EventHandler GuildUpdated; + public event EventHandler CurrentUserUpdated; + public event EventHandler UserJoined, UserLeft; + public event EventHandler UserBanned, UserUnbanned; + public event EventHandler UserUpdated; + public event EventHandler UserIsTyping; + + private readonly Logger _discordLogger, _gatewayLogger; + private readonly SemaphoreSlim _connectionLock; + private readonly RestClientProvider _restClientProvider; + private readonly LogManager _log; + private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; + private readonly bool _enablePreUpdateEvents; + private readonly int _largeThreshold; + private readonly int _totalShards; + private IReadOnlyDictionary _voiceRegions; + private CancellationTokenSource _cancelTokenSource; + private bool _isDisposed; + private SelfUser _currentUser; + private ConcurrentDictionary _guilds; + private ConcurrentDictionary _channels; + private ConcurrentDictionary _dmChannels; //Key = RecipientId + private ConcurrentDictionary _users; + + public int ShardId { get; } + public bool IsLoggedIn { get; private set; } + public API.DiscordApiClient ApiClient { get; private set; } + public SelfUser CurrentUser { get; private set; } + //public GatewaySocket GatewaySocket { get; private set; } + internal int MessageCacheSize { get; private set; } + internal bool UsePermissionCache { get; private set; } + + public TokenType AuthTokenType => ApiClient.AuthTokenType; + public IRestClient RestClient => ApiClient.RestClient; + public IRequestQueue RequestQueue => ApiClient.RequestQueue; + public IEnumerable Guilds => _guilds.Values; + public IEnumerable Channels => _channels.Values; + public IEnumerable DMChannels => _dmChannels.Values; + public IEnumerable VoiceRegions => _voiceRegions.Values; - public SelfUser CurrentUser + //public bool IsConnected => GatewaySocket.State == ConnectionState.Connected; + + public DiscordClient(DiscordSocketConfig config = null) { - get - { - throw new NotImplementedException(); - } + if (config == null) + config = new DiscordSocketConfig(); + + _restClientProvider = config.RestClientProvider; + ShardId = config.ShardId; + _totalShards = config.TotalShards; + + _connectionTimeout = config.ConnectionTimeout; + _reconnectDelay = config.ReconnectDelay; + _failedReconnectDelay = config.FailedReconnectDelay; + + MessageCacheSize = config.MessageCacheSize; + UsePermissionCache = config.UsePermissionsCache; + _enablePreUpdateEvents = config.EnablePreUpdateEvents; + _largeThreshold = config.LargeThreshold; + + _log = new LogManager(config.LogLevel); + _log.Message += (s, e) => Log.Raise(this, e); + _discordLogger = _log.CreateLogger("Discord"); + _gatewayLogger = _log.CreateLogger("Gateway"); + + _connectionLock = new SemaphoreSlim(1, 1); + ApiClient = new API.DiscordApiClient(_restClientProvider); + ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + + _channels = new ConcurrentDictionary(1, 100); + _dmChannels = new ConcurrentDictionary(1, 100); + _guilds = new ConcurrentDictionary(1, 25); + _users = new ConcurrentDictionary(1, 250); } - public TokenType AuthTokenType + public async Task Login(string email, string password) { - get + await _connectionLock.WaitAsync().ConfigureAwait(false); + try { - throw new NotImplementedException(); + await LoginInternal(email, password).ConfigureAwait(false); } + finally { _connectionLock.Release(); } } - - public DiscordAPIClient APIClient + public async Task Login(TokenType tokenType, string token, bool validateToken = true) { - get + await _connectionLock.WaitAsync().ConfigureAwait(false); + try { - throw new NotImplementedException(); + await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); } + finally { _connectionLock.Release(); } } - - public IRequestQueue RequestQueue + private async Task LoginInternal(string email, string password) { - get + if (IsLoggedIn) + await LogoutInternal().ConfigureAwait(false); + try { - throw new NotImplementedException(); + _cancelTokenSource = new CancellationTokenSource(); + + var args = new LoginParams { Email = email, Password = password }; + await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); + await CompleteLogin(false).ConfigureAwait(false); } + catch { await LogoutInternal().ConfigureAwait(false); throw; } } - - public IRestClient RestClient + private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) { - get + if (IsLoggedIn) + await LogoutInternal().ConfigureAwait(false); + try { - throw new NotImplementedException(); + _cancelTokenSource = new CancellationTokenSource(); + + await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); + await CompleteLogin(validateToken).ConfigureAwait(false); } + catch { await LogoutInternal().ConfigureAwait(false); throw; } } - - public Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) + private async Task CompleteLogin(bool validateToken) { - throw new NotImplementedException(); - } + if (validateToken) + { + try + { + await ApiClient.ValidateToken().ConfigureAwait(false); + var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); + _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); - public Task GetChannel(ulong id) - { - throw new NotImplementedException(); - } + } + catch { await ApiClient.Logout().ConfigureAwait(false); } + } - public Task> GetConnections() - { - throw new NotImplementedException(); + IsLoggedIn = true; + LoggedIn.Raise(this); } - public Task GetCurrentUser() + public async Task Logout() { - throw new NotImplementedException(); + _cancelTokenSource?.Cancel(); + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LogoutInternal().ConfigureAwait(false); + } + finally { _connectionLock.Release(); } } - - public Task> GetDMChannels() + private async Task LogoutInternal() { - throw new NotImplementedException(); - } + bool wasLoggedIn = IsLoggedIn; - public Task GetGuild(ulong id) - { - throw new NotImplementedException(); - } + if (_cancelTokenSource != null) + { + try { _cancelTokenSource.Cancel(false); } + catch { } + } - public Task> GetGuilds() - { - throw new NotImplementedException(); + await ApiClient.Logout().ConfigureAwait(false); + _channels.Clear(); + _dmChannels.Clear(); + _guilds.Clear(); + _users.Clear(); + _currentUser = null; + + if (wasLoggedIn) + { + IsLoggedIn = false; + LoggedOut.Raise(this); + } } - public Task GetInvite(string inviteIdOrXkcd) + public async Task> GetConnections() { - throw new NotImplementedException(); + var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false); + return models.Select(x => new Connection(x)); } - public Task GetOptimalVoiceRegion() + public IChannel GetChannel(ulong id) { - throw new NotImplementedException(); + IChannel channel; + if (_channels.TryGetValue(id, out channel)) + return channel; + return null; } - public Task GetUser(ulong id) + public async Task GetInvite(string inviteIdOrXkcd) { - throw new NotImplementedException(); + var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + if (model != null) + return new Invite(this, model); + return null; } - public Task GetUser(string username, ushort discriminator) + public Guild GetGuild(ulong id) { - throw new NotImplementedException(); + Guild guild; + if (_guilds.TryGetValue(id, out guild)) + return guild; + return null; } - - public Task GetVoiceRegion(string id) + public async Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) { - throw new NotImplementedException(); + var args = new CreateGuildParams(); + var model = await ApiClient.CreateGuild(args).ConfigureAwait(false); + return new Guild(this, model); } - public Task> GetVoiceRegions() + public User GetUser(ulong id) { - throw new NotImplementedException(); + User user; + if (_users.TryGetValue(id, out user)) + return user; + return null; } - - public Task Login(string email, string password) + public User GetUser(string username, ushort discriminator) { - throw new NotImplementedException(); + return _users.Where(x => x.Value.Discriminator == discriminator && x.Value.Username == username).Select(x => x.Value).FirstOrDefault(); } - - public Task Login(TokenType tokenType, string token, bool validateToken = true) + public async Task> QueryUsers(string query, int limit) { - throw new NotImplementedException(); + var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false); + return models.Select(x => new User(this, x)); } - public Task Logout() + public VoiceRegion GetVoiceRegion(string id) { - throw new NotImplementedException(); + VoiceRegion region; + if (_voiceRegions.TryGetValue(id, out region)) + return region; + return null; } - public Task> QueryUsers(string query, int limit) + void Dispose(bool disposing) { - throw new NotImplementedException(); + if (!_isDisposed) + { + if (disposing) + _cancelTokenSource.Dispose(); + _isDisposed = true; + } } + public void Dispose() => Dispose(true); + + API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; + + Task IDiscordClient.GetChannel(ulong id) + => Task.FromResult(GetChannel(id)); + Task> IDiscordClient.GetDMChannels() + => Task.FromResult>(DMChannels); + async Task> IDiscordClient.GetConnections() + => await GetConnections().ConfigureAwait(false); + async Task IDiscordClient.GetInvite(string inviteIdOrXkcd) + => await GetInvite(inviteIdOrXkcd).ConfigureAwait(false); + Task IDiscordClient.GetGuild(ulong id) + => Task.FromResult(GetGuild(id)); + Task> IDiscordClient.GetGuilds() + => Task.FromResult>(Guilds); + async Task IDiscordClient.CreateGuild(string name, IVoiceRegion region, Stream jpegIcon) + => await CreateGuild(name, region, jpegIcon).ConfigureAwait(false); + Task IDiscordClient.GetUser(ulong id) + => Task.FromResult(GetUser(id)); + Task IDiscordClient.GetUser(string username, ushort discriminator) + => Task.FromResult(GetUser(username, discriminator)); + Task IDiscordClient.GetCurrentUser() + => Task.FromResult(CurrentUser); + async Task> IDiscordClient.QueryUsers(string query, int limit) + => await QueryUsers(query, limit).ConfigureAwait(false); + Task> IDiscordClient.GetVoiceRegions() + => Task.FromResult>(VoiceRegions); + Task IDiscordClient.GetVoiceRegion(string id) + => Task.FromResult(GetVoiceRegion(id)); } } diff --git a/src/Discord.Net/WebSocket/DiscordSocketConfig.cs b/src/Discord.Net/WebSocket/DiscordSocketConfig.cs new file mode 100644 index 000000000..abe76a0d7 --- /dev/null +++ b/src/Discord.Net/WebSocket/DiscordSocketConfig.cs @@ -0,0 +1,39 @@ +using Discord.Net.WebSockets; + +namespace Discord.WebSocket +{ + public class DiscordSocketConfig : DiscordConfig + { + /// Gets or sets the id for this shard. Must be less than TotalShards. + public int ShardId { get; set; } = 0; + /// Gets or sets the total number of shards for this application. + public int TotalShards { get; set; } = 1; + + /// Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. + public int ConnectionTimeout { get; set; } = 30000; + /// Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. + public int ReconnectDelay { get; set; } = 1000; + /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. + public int FailedReconnectDelay { get; set; } = 15000; + + /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. + public int MessageCacheSize { get; set; } = 100; + /// + /// Gets or sets whether the permissions cache should be used. + /// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage. + /// + public bool UsePermissionsCache { get; set; } = true; + /// Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. + public bool EnablePreUpdateEvents { get; set; } = true; + /// + /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. + /// Decreasing this may reduce CPU usage while increasing login time and network usage. + /// + public int LargeThreshold { get; set; } = 250; + + //Engines + + /// Gets or sets the provider used to generate new websocket connections. + public WebSocketProvider WebSocketProvider { get; set; } = null; + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/Channel.cs b/src/Discord.Net/WebSocket/Entities/Channels/Channel.cs new file mode 100644 index 000000000..b645d1c20 --- /dev/null +++ b/src/Discord.Net/WebSocket/Entities/Channels/Channel.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + //TODO: Look into Internal abstract pattern - can we get rid of this? + public abstract class Channel : IChannel + { + /// + public ulong Id { get; private set; } + public IEnumerable Users => GetUsersInternal(); + + /// + public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); + + internal Channel(ulong id) + { + Id = id; + } + + /// + public User GetUser(ulong id) + => GetUserInternal(id); + + protected abstract User GetUserInternal(ulong id); + protected abstract IEnumerable GetUsersInternal(); + + Task> IChannel.GetUsers() + => Task.FromResult>(GetUsersInternal()); + Task> IChannel.GetUsers(int limit, int offset) + => Task.FromResult>(GetUsersInternal().Skip(offset).Take(limit)); + Task IChannel.GetUser(ulong id) + => Task.FromResult(GetUserInternal(id)); + } +} diff --git a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs index abc5dd725..bb93566af 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/DMChannel.cs @@ -1,5 +1,4 @@ using Discord.API.Rest; -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -11,41 +10,34 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class DMChannel : IDMChannel + public class DMChannel : Channel, IDMChannel { private readonly MessageCache _messages; - - /// - public ulong Id { get; } + internal DiscordClient Discord { get; } /// - public DMUser Recipient { get; private set; } - - /// - public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); + public User Recipient { get; private set; } + /// - public IEnumerable Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + public new IEnumerable Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); public IEnumerable CachedMessages => _messages.Messages; - internal DMChannel(DiscordClient discord, Model model) + internal DMChannel(DiscordClient discord, User recipient, Model model) + : base(model.Id) { - Id = model.Id; Discord = discord; + Recipient = recipient; _messages = new MessageCache(Discord, this); Update(model); } private void Update(Model model) { - if (Recipient == null) - Recipient = new DMUser(this, model.Recipient); - else - Recipient.Update(model.Recipient); + Recipient.Update(model.Recipient); } - /// - public IUser GetUser(ulong id) + protected override User GetUserInternal(ulong id) { if (id == Recipient.Id) return Recipient; @@ -54,6 +46,10 @@ namespace Discord.WebSocket else return null; } + protected override IEnumerable GetUsersInternal() + { + return Users; + } /// Gets the message from this channel's cache with the given id, or null if none was found. public Message GetCachedMessage(ulong id) @@ -75,8 +71,8 @@ namespace Discord.WebSocket public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.CreateDMMessage(Id, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.CreateDMMessage(Id, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Id), model); } /// public async Task SendFile(string filePath, string text = null, bool isTTS = false) @@ -85,49 +81,49 @@ namespace Discord.WebSocket using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadDMFile(Id, file, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.UploadDMFile(Id, file, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Id), model); } } /// public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.UploadDMFile(Id, stream, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Id), model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.APIClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.ApiClient.DeleteDMMessages(Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } /// public async Task Close() { - await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); } /// public override string ToString() => '@' + Recipient.ToString(); private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - IDMUser IDMChannel.Recipient => Recipient; + IUser IDMChannel.Recipient => Recipient; IEnumerable IMessageChannel.CachedMessages => CachedMessages; Task> IChannel.GetUsers() - => Task.FromResult(Users); + => Task.FromResult>(Users); Task> IChannel.GetUsers(int limit, int offset) - => Task.FromResult(Users.Skip(offset).Take(limit)); + => Task.FromResult>(Users.Skip(offset).Take(limit)); Task IChannel.GetUser(ulong id) - => Task.FromResult(GetUser(id)); + => Task.FromResult(GetUser(id)); Task IMessageChannel.GetCachedMessage(ulong id) => Task.FromResult(GetCachedMessage(id)); async Task> IMessageChannel.GetMessages(int limit) diff --git a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs index 5d8a6db68..db571577a 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/GuildChannel.cs @@ -8,13 +8,11 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { - public abstract class GuildChannel : IGuildChannel + public abstract class GuildChannel : Channel, IGuildChannel { private ConcurrentDictionary _overwrites; internal PermissionsCache _permissions; - - /// - public ulong Id { get; } + /// Gets the guild this channel is a member of. public Guild Guild { get; } @@ -22,17 +20,15 @@ namespace Discord.WebSocket public string Name { get; private set; } /// public int Position { get; private set; } - public abstract IEnumerable Users { get; } - - /// - public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); + public new abstract IEnumerable Users { get; } + /// public IReadOnlyDictionary PermissionOverwrites => _overwrites; internal DiscordClient Discord => Guild.Discord; internal GuildChannel(Guild guild, Model model) + : base(model.Id) { - Id = model.Id; Guild = guild; Update(model); @@ -57,11 +53,19 @@ namespace Discord.WebSocket var args = new ModifyGuildChannelParams(); func(args); - await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } /// Gets a user in this channel with the given id. - public abstract GuildUser GetUser(ulong id); + public new abstract GuildUser GetUser(ulong id); + protected override User GetUserInternal(ulong id) + { + return GetUser(id).GlobalUser; + } + protected override IEnumerable GetUsersInternal() + { + return Users.Select(x => x.GlobalUser); + } /// public OverwritePermissions? GetPermissionOverwrite(IUser user) @@ -82,7 +86,7 @@ namespace Discord.WebSocket /// Downloads a collection of all invites to this channel. public async Task> GetInvites() { - var models = await Discord.APIClient.GetChannelInvites(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetChannelInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } @@ -90,23 +94,23 @@ namespace Discord.WebSocket public async Task AddPermissionOverwrite(IUser user, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.APIClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyChannelPermissions(Id, user.Id, args).ConfigureAwait(false); } /// public async Task AddPermissionOverwrite(IRole role, OverwritePermissions perms) { var args = new ModifyChannelPermissionsParams { Allow = perms.AllowValue, Deny = perms.DenyValue }; - await Discord.APIClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyChannelPermissions(Id, role.Id, args).ConfigureAwait(false); } /// public async Task RemovePermissionOverwrite(IUser user) { - await Discord.APIClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannelPermission(Id, user.Id).ConfigureAwait(false); } /// public async Task RemovePermissionOverwrite(IRole role) { - await Discord.APIClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannelPermission(Id, role.Id).ConfigureAwait(false); } /// Creates a new invite to this channel. @@ -123,14 +127,14 @@ namespace Discord.WebSocket Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.APIClient.CreateChannelInvite(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } /// public async Task Delete() { - await Discord.APIClient.DeleteChannel(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteChannel(Id).ConfigureAwait(false); } /// diff --git a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs index 760fd6c84..ade45276e 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/TextChannel.cs @@ -42,7 +42,7 @@ namespace Discord.WebSocket var args = new ModifyTextChannelParams(); func(args); - await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } /// Gets the message from this channel's cache with the given id, or null if none was found. @@ -73,8 +73,8 @@ namespace Discord.WebSocket public async Task SendMessage(string text, bool isTTS = false) { var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.CreateMessage(Guild.Id, Id, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Id), model); } /// public async Task SendFile(string filePath, string text = null, bool isTTS = false) @@ -83,28 +83,28 @@ namespace Discord.WebSocket using (var file = File.OpenRead(filePath)) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, file, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Author.Id), model); } } /// public async Task SendFile(Stream stream, string filename, string text = null, bool isTTS = false) { var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; - var model = await Discord.APIClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); - return new Message(this, model); + var model = await Discord.ApiClient.UploadFile(Guild.Id, Id, stream, args).ConfigureAwait(false); + return new Message(this, GetUser(model.Author.Id), model); } /// public async Task DeleteMessages(IEnumerable messages) { - await Discord.APIClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); + await Discord.ApiClient.DeleteMessages(Guild.Id, Id, new DeleteMessagesParam { MessageIds = messages.Select(x => x.Id) }).ConfigureAwait(false); } /// public async Task TriggerTyping() { - await Discord.APIClient.TriggerTypingIndicator(Id).ConfigureAwait(false); + await Discord.ApiClient.TriggerTypingIndicator(Id).ConfigureAwait(false); } private string DebuggerDisplay => $"{Name} ({Id}, Text)"; diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index d9cc49500..eec317352 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -34,7 +34,7 @@ namespace Discord.WebSocket var args = new ModifyVoiceChannelParams(); func(args); - await Discord.APIClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildChannel(Id, args).ConfigureAwait(false); } public override GuildUser GetUser(ulong id) diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 8ccb68fe3..1b0f87f2a 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -6,18 +6,21 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Guild; -using EmbedModel = Discord.API.GuildEmbed; -using RoleModel = Discord.API.Role; using System.Diagnostics; namespace Discord.WebSocket { /// Represents a Discord guild (called a server in the official client). [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class Guild : IGuild + public class Guild : IGuild, IUserGuild { + private ConcurrentDictionary _channels; + private ConcurrentDictionary _members; private ConcurrentDictionary _roles; + private ulong _ownerId; + private ulong? _afkChannelId, _embedChannelId; private string _iconId, _splashId; + private int _userCount; /// public ulong Id { get; } @@ -31,34 +34,52 @@ namespace Discord.WebSocket public bool IsEmbeddable { get; private set; } /// public int VerificationLevel { get; private set; } - public int UserCount { get; private set; } - - /// - public ulong? AFKChannelId { get; private set; } - /// - public ulong? EmbedChannelId { get; private set; } - /// - public ulong OwnerId { get; private set; } + /// - public string VoiceRegionId { get; private set; } + public VoiceRegion VoiceRegion { get; private set; } /// public IReadOnlyList Emojis { get; private set; } /// public IReadOnlyList Features { get; private set; } - + /// public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); + /// public string IconUrl => API.CDN.GetGuildIconUrl(Id, _iconId); /// public string SplashUrl => API.CDN.GetGuildSplashUrl(Id, _splashId); - /// - public ulong DefaultChannelId => Id; - /// + + /// Gets the number of channels in this guild. + public int ChannelCount => _channels.Count; + /// Gets the number of roles in this guild. + public int RoleCount => _roles.Count; + /// Gets the number of users in this guild. + public int UserCount => _userCount; + /// Gets the number of users downloaded for this guild so far. + internal int CurrentUserCount => _members.Count; + + /// Gets the the role representing all users in a guild. public Role EveryoneRole => GetRole(Id); + public GuildUser CurrentUser => GetUser(Discord.CurrentUser.Id); + /// Gets the user that created this guild. + public GuildUser Owner => GetUser(_ownerId); + /// Gets the default channel for this guild. + public TextChannel DefaultChannel => GetChannel(Id) as TextChannel; + /// Gets the AFK voice channel for this guild. + public VoiceChannel AFKChannel => GetChannel(_afkChannelId.GetValueOrDefault(0)) as VoiceChannel; + /// Gets the embed channel for this guild. + public IChannel EmbedChannel => GetChannel(_embedChannelId.GetValueOrDefault(0)); //TODO: Is this text or voice? + /// Gets a collection of all channels in this guild. + public IEnumerable Channels => _channels.Select(x => x.Value); + /// Gets a collection of text channels in this guild. + public IEnumerable TextChannels => _channels.Select(x => x.Value).OfType(); + /// Gets a collection of voice channels in this guild. + public IEnumerable VoiceChannels => _channels.Select(x => x.Value).OfType(); /// Gets a collection of all roles in this guild. public IEnumerable Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty(); - public IEnumerable Users => Array.Empty(); + /// Gets a collection of all users in this guild. + public IEnumerable Users => _members.Select(x => x.Value); internal Guild(DiscordClient discord, Model model) { @@ -69,15 +90,15 @@ namespace Discord.WebSocket } private void Update(Model model) { - AFKChannelId = model.AFKChannelId; + _afkChannelId = model.AFKChannelId; AFKTimeout = model.AFKTimeout; - EmbedChannelId = model.EmbedChannelId; + _embedChannelId = model.EmbedChannelId; IsEmbeddable = model.EmbedEnabled; Features = model.Features; _iconId = model.Icon; Name = model.Name; - OwnerId = model.OwnerId; - VoiceRegionId = model.Region; + _ownerId = model.OwnerId; + VoiceRegion = Discord.GetVoiceRegion(model.Region); _splashId = model.Splash; VerificationLevel = model.VerificationLevel; @@ -99,20 +120,6 @@ namespace Discord.WebSocket } _roles = roles; } - private void Update(EmbedModel model) - { - IsEmbeddable = model.Enabled; - EmbedChannelId = model.ChannelId; - } - private void Update(IEnumerable models) - { - Role role; - foreach (var model in models) - { - if (_roles.TryGetValue(model.Id, out role)) - role.Update(model); - } - } /// public async Task Modify(Action func) @@ -121,7 +128,7 @@ namespace Discord.WebSocket var args = new ModifyGuildParams(); func(args); - await Discord.APIClient.ModifyGuild(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuild(Id, args).ConfigureAwait(false); } /// public async Task ModifyEmbed(Action func) @@ -130,75 +137,66 @@ namespace Discord.WebSocket var args = new ModifyGuildEmbedParams(); func(args); - await Discord.APIClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildEmbed(Id, args).ConfigureAwait(false); } /// public async Task ModifyChannels(IEnumerable args) { - await Discord.APIClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildChannels(Id, args).ConfigureAwait(false); } /// public async Task ModifyRoles(IEnumerable args) { - await Discord.APIClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildRoles(Id, args).ConfigureAwait(false); } /// public async Task Leave() { - await Discord.APIClient.LeaveGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.LeaveGuild(Id).ConfigureAwait(false); } /// public async Task Delete() { - await Discord.APIClient.DeleteGuild(Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuild(Id).ConfigureAwait(false); } /// public async Task> GetBans() { - var models = await Discord.APIClient.GetGuildBans(Id).ConfigureAwait(false); - return models.Select(x => new PublicUser(Discord, x)); + var models = await Discord.ApiClient.GetGuildBans(Id).ConfigureAwait(false); + return models.Select(x => new User(Discord, x)); } /// public Task AddBan(IUser user, int pruneDays = 0) => AddBan(user, pruneDays); /// public async Task AddBan(ulong userId, int pruneDays = 0) { - var args = new CreateGuildBanParams() - { - PruneDays = pruneDays - }; - await Discord.APIClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); + var args = new CreateGuildBanParams() { PruneDays = pruneDays }; + await Discord.ApiClient.CreateGuildBan(Id, userId, args).ConfigureAwait(false); } /// public Task RemoveBan(IUser user) => RemoveBan(user.Id); /// public async Task RemoveBan(ulong userId) { - await Discord.APIClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); + await Discord.ApiClient.RemoveGuildBan(Id, userId).ConfigureAwait(false); } /// Gets the channel in this guild with the provided id, or null if not found. - public async Task GetChannel(ulong id) + public GuildChannel GetChannel(ulong id) { - var model = await Discord.APIClient.GetChannel(Id, id).ConfigureAwait(false); - if (model != null) - return ToChannel(model); + GuildChannel channel; + if (_channels.TryGetValue(id, out channel)) + return channel; return null; } - /// Gets a collection of all channels in this guild. - public async Task> GetChannels() - { - var models = await Discord.APIClient.GetGuildChannels(Id).ConfigureAwait(false); - return models.Select(x => ToChannel(x)); - } /// Creates a new text channel. public async Task CreateTextChannel(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams() { Name = name, Type = ChannelType.Text }; - var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new TextChannel(this, model); } /// Creates a new voice channel. @@ -207,28 +205,22 @@ namespace Discord.WebSocket if (name == null) throw new ArgumentNullException(nameof(name)); var args = new CreateGuildChannelParams { Name = name, Type = ChannelType.Voice }; - var model = await Discord.APIClient.CreateGuildChannel(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildChannel(Id, args).ConfigureAwait(false); return new VoiceChannel(this, model); } - - /// Gets a collection of all integrations attached to this guild. - public async Task> GetIntegrations() - { - var models = await Discord.APIClient.GetGuildIntegrations(Id).ConfigureAwait(false); - return models.Select(x => new GuildIntegration(this, x)); - } + /// Creates a new integration for this guild. public async Task CreateIntegration(ulong id, string type) { var args = new CreateGuildIntegrationParams { Id = id, Type = type }; - var model = await Discord.APIClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildIntegration(Id, args).ConfigureAwait(false); return new GuildIntegration(this, model); } /// Gets a collection of all invites to this guild. public async Task> GetInvites() { - var models = await Discord.APIClient.GetGuildInvites(Id).ConfigureAwait(false); + var models = await Discord.ApiClient.GetGuildInvites(Id).ConfigureAwait(false); return models.Select(x => new InviteMetadata(Discord, x)); } /// Creates a new invite to this guild. @@ -244,7 +236,7 @@ namespace Discord.WebSocket Temporary = isTemporary, XkcdPass = withXkcd }; - var model = await Discord.APIClient.CreateChannelInvite(DefaultChannelId, args).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateChannelInvite(Id, args).ConfigureAwait(false); return new InviteMetadata(Discord, model); } @@ -262,7 +254,7 @@ namespace Discord.WebSocket { if (name == null) throw new ArgumentNullException(nameof(name)); - var model = await Discord.APIClient.CreateGuildRole(Id).ConfigureAwait(false); + var model = await Discord.ApiClient.CreateGuildRole(Id).ConfigureAwait(false); var role = new Role(this, model); await role.Modify(x => @@ -275,43 +267,23 @@ namespace Discord.WebSocket return role; } - - /// Gets a collection of all users in this guild. - public async Task> GetUsers() - { - var args = new GetGuildMembersParams(); - var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); - return models.Select(x => new GuildUser(this, x)); - } - /// Gets a paged collection of all users in this guild. - public async Task> GetUsers(int limit, int offset) - { - var args = new GetGuildMembersParams { Limit = limit, Offset = offset }; - var models = await Discord.APIClient.GetGuildMembers(Id, args).ConfigureAwait(false); - return models.Select(x => new GuildUser(this, x)); - } + /// Gets the user in this guild with the provided id, or null if not found. - public async Task GetUser(ulong id) + public GuildUser GetUser(ulong id) { - var model = await Discord.APIClient.GetGuildMember(Id, id).ConfigureAwait(false); - if (model != null) - return new GuildUser(this, model); + GuildUser user; + if (_members.TryGetValue(id, out user)) + return user; return null; } - /// Gets a the current user. - public async Task GetCurrentUser() - { - var currentUser = await Discord.GetCurrentUser().ConfigureAwait(false); - return await GetUser(currentUser.Id).ConfigureAwait(false); - } public async Task PruneUsers(int days = 30, bool simulate = false) { var args = new GuildPruneParams() { Days = days }; GetGuildPruneCountResponse model; if (simulate) - model = await Discord.APIClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.GetGuildPruneCount(Id, args).ConfigureAwait(false); else - model = await Discord.APIClient.BeginGuildPrune(Id, args).ConfigureAwait(false); + model = await Discord.ApiClient.BeginGuildPrune(Id, args).ConfigureAwait(false); return model.Pruned; } @@ -331,15 +303,22 @@ namespace Discord.WebSocket private string DebuggerDisplay => $"{Name} ({Id})"; IEnumerable IGuild.Emojis => Emojis; - ulong IGuild.EveryoneRoleId => EveryoneRole.Id; IEnumerable IGuild.Features => Features; + ulong? IGuild.AFKChannelId => _afkChannelId; + ulong IGuild.DefaultChannelId => Id; + ulong? IGuild.EmbedChannelId => _embedChannelId; + ulong IGuild.EveryoneRoleId => EveryoneRole.Id; + ulong IGuild.OwnerId => _ownerId; + string IGuild.VoiceRegionId => VoiceRegion.Id; + bool IUserGuild.IsOwner => CurrentUser.Id == _ownerId; + GuildPermissions IUserGuild.Permissions => CurrentUser.GuildPermissions; async Task> IGuild.GetBans() => await GetBans().ConfigureAwait(false); - async Task IGuild.GetChannel(ulong id) - => await GetChannel(id).ConfigureAwait(false); - async Task> IGuild.GetChannels() - => await GetChannels().ConfigureAwait(false); + Task IGuild.GetChannel(ulong id) + => Task.FromResult(GetChannel(id)); + Task> IGuild.GetChannels() + => Task.FromResult>(Channels); async Task IGuild.CreateInvite(int? maxAge, int? maxUses, bool isTemporary, bool withXkcd) => await CreateInvite(maxAge, maxUses, isTemporary, withXkcd).ConfigureAwait(false); async Task IGuild.CreateRole(string name, GuildPermissions? permissions, Color? color, bool isHoisted) @@ -354,12 +333,12 @@ namespace Discord.WebSocket => Task.FromResult(GetRole(id)); Task> IGuild.GetRoles() => Task.FromResult>(Roles); - async Task IGuild.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); - async Task IGuild.GetCurrentUser() - => await GetCurrentUser().ConfigureAwait(false); - async Task> IGuild.GetUsers() - => await GetUsers().ConfigureAwait(false); + Task IGuild.GetUser(ulong id) + => Task.FromResult(GetUser(id)); + Task IGuild.GetCurrentUser() + => Task.FromResult(CurrentUser); + Task> IGuild.GetUsers() + => Task.FromResult>(Users); Task IUpdateable.Update() => Task.CompletedTask; } diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs index fbb810e90..61610a0ae 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/GuildIntegration.cs @@ -31,7 +31,7 @@ namespace Discord.WebSocket /// public Role Role { get; private set; } /// - public User User { get; private set; } + public GuildUser User { get; private set; } /// public IntegrationAccount Account { get; private set; } internal DiscordClient Discord => Guild.Discord; @@ -54,13 +54,13 @@ namespace Discord.WebSocket SyncedAt = model.SyncedAt; Role = Guild.GetRole(model.RoleId); - User = new PublicUser(Discord, model.User); + User = Guild.GetUser(model.User.Id); } /// public async Task Delete() { - await Discord.APIClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } /// public async Task Modify(Action func) @@ -69,12 +69,12 @@ namespace Discord.WebSocket var args = new ModifyGuildIntegrationParams(); func(args); - await Discord.APIClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildIntegration(Guild.Id, Id, args).ConfigureAwait(false); } /// public async Task Sync() { - await Discord.APIClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.SyncGuildIntegration(Guild.Id, Id).ConfigureAwait(false); } public override string ToString() => Name; diff --git a/src/Discord.Net/WebSocket/Entities/Message.cs b/src/Discord.Net/WebSocket/Entities/Message.cs index dcd1dbf36..af42298f8 100644 --- a/src/Discord.Net/WebSocket/Entities/Message.cs +++ b/src/Discord.Net/WebSocket/Entities/Message.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; namespace Discord.WebSocket { + //TODO: Support mention_roles [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Message : IMessage { @@ -28,33 +30,36 @@ namespace Discord.WebSocket /// public IMessageChannel Channel { get; } /// - public User Author { get; } + public IUser Author { get; } /// public IReadOnlyList Attachments { get; private set; } /// public IReadOnlyList Embeds { get; private set; } /// - public IReadOnlyList MentionedUsers { get; private set; } + public IReadOnlyList MentionedUsers { get; private set; } /// - public IReadOnlyList MentionedChannelIds { get; private set; } + public IReadOnlyList MentionedChannels { get; private set; } /// - public IReadOnlyList MentionedRoleIds { get; private set; } + public IReadOnlyList MentionedRoles { get; private set; } /// public DateTime CreatedAt => DateTimeUtils.FromSnowflake(Id); internal DiscordClient Discord => (Channel as TextChannel)?.Discord ?? (Channel as DMChannel).Discord; - internal Message(IMessageChannel channel, Model model) + internal Message(IMessageChannel channel, IUser author, Model model) { Id = model.Id; Channel = channel; - Author = new PublicUser(Discord, model.Author); + Author = author; Update(model); } private void Update(Model model) { + var guildChannel = Channel as GuildChannel; + var guild = guildChannel?.Guild; + IsTTS = model.IsTextToSpeech; Timestamp = model.Timestamp; EditedTimestamp = model.EditedTimestamp; @@ -80,38 +85,38 @@ namespace Discord.WebSocket else Embeds = Array.Empty(); - if (model.Mentions.Length > 0) + if (guildChannel != null && model.Mentions.Length > 0) { - var discord = Discord; - var builder = ImmutableArray.CreateBuilder(model.Mentions.Length); + var builder = ImmutableArray.CreateBuilder(model.Mentions.Length); for (int i = 0; i < model.Mentions.Length; i++) - builder.Add(new PublicUser(discord, model.Mentions[i])); + { + var user = guild.GetUser(model.Mentions[i].Id); + if (user != null) + builder.Add(user); + } MentionedUsers = builder.ToArray(); } else - MentionedUsers = Array.Empty(); - MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); - MentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); - if (model.IsMentioningEveryone) + MentionedUsers = Array.Empty(); + + if (guildChannel != null/* && model.Content != null*/) { - ulong? guildId = (Channel as IGuildChannel)?.Guild.Id; - if (guildId != null) - { - if (MentionedRoleIds.Count == 0) - MentionedRoleIds = ImmutableArray.Create(guildId.Value); - else - { - var builder = ImmutableArray.CreateBuilder(MentionedRoleIds.Count + 1); - builder.AddRange(MentionedRoleIds); - builder.Add(guildId.Value); - MentionedRoleIds = builder.ToImmutable(); - } - } + MentionedChannels = MentionUtils.GetChannelMentions(model.Content).Select(x => guild.GetChannel(x)).Where(x => x != null).ToImmutableArray(); + + var mentionedRoles = MentionUtils.GetRoleMentions(model.Content).Select(x => guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); + if (model.IsMentioningEveryone) + mentionedRoles = mentionedRoles.Add(guild.EveryoneRole); + MentionedRoles = mentionedRoles; + } + else + { + MentionedChannels = Array.Empty(); + MentionedRoles = Array.Empty(); } Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); - Author.Update(model.Author); + //Author.Update(model.Author); //TODO: Uncomment this somehow } /// @@ -124,9 +129,9 @@ namespace Discord.WebSocket var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.APIClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyMessage(guildChannel.Guild.Id, Channel.Id, Id, args).ConfigureAwait(false); else - await Discord.APIClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyDMMessage(Channel.Id, Id, args).ConfigureAwait(false); } /// @@ -134,18 +139,17 @@ namespace Discord.WebSocket { var guildChannel = Channel as GuildChannel; if (guildChannel != null) - await Discord.APIClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteMessage(guildChannel.Id, Channel.Id, Id).ConfigureAwait(false); else - await Discord.APIClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); + await Discord.ApiClient.DeleteDMMessage(Channel.Id, Id).ConfigureAwait(false); } public override string ToString() => Text; private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; IUser IMessage.Author => Author; - IReadOnlyList IMessage.Attachments => Attachments; - IReadOnlyList IMessage.Embeds => Embeds; - IReadOnlyList IMessage.MentionedChannelIds => MentionedChannelIds; IReadOnlyList IMessage.MentionedUsers => MentionedUsers; + IReadOnlyList IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); + IReadOnlyList IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net/WebSocket/Entities/Role.cs b/src/Discord.Net/WebSocket/Entities/Role.cs index 24af5f1d8..52bef2b1e 100644 --- a/src/Discord.Net/WebSocket/Entities/Role.cs +++ b/src/Discord.Net/WebSocket/Entities/Role.cs @@ -35,6 +35,7 @@ namespace Discord.WebSocket public bool IsEveryone => Id == Guild.Id; /// public string Mention => MentionUtils.Mention(this); + public IEnumerable Users => Guild.Users.Where(x => x.Roles.Any(y => y.Id == Id)); internal DiscordClient Discord => Guild.Discord; internal Role(Guild guild, Model model) @@ -60,23 +61,19 @@ namespace Discord.WebSocket var args = new ModifyGuildRoleParams(); func(args); - await Discord.APIClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildRole(Guild.Id, Id, args).ConfigureAwait(false); } /// Deletes this message. public async Task Delete() - => await Discord.APIClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); + => await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false); /// public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; ulong IRole.GuildId => Guild.Id; - - async Task> IRole.GetUsers() - { - //A tad hacky, but it works - var models = await Discord.APIClient.GetGuildMembers(Guild.Id, new GetGuildMembersParams()).ConfigureAwait(false); - return models.Where(x => x.Roles.Contains(Id)).Select(x => new GuildUser(Guild, x)); - } + + Task> IRole.GetUsers() + => Task.FromResult>(Users); } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs b/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs deleted file mode 100644 index 4b300ce76..000000000 --- a/src/Discord.Net/WebSocket/Entities/Users/DMUser.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Model = Discord.API.User; - -namespace Discord.WebSocket -{ - public class DMUser : User, IDMUser - { - /// - public DMChannel Channel { get; } - - internal override DiscordClient Discord => Channel.Discord; - - internal DMUser(DMChannel channel, Model model) - : base(model) - { - Channel = channel; - } - - IDMChannel IDMUser.Channel => Channel; - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs index 0dde4a8c5..c0caec225 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/GuildUser.cs @@ -8,11 +8,12 @@ using Model = Discord.API.GuildMember; namespace Discord.WebSocket { - public class GuildUser : User, IGuildUser + public class GuildUser : IGuildUser { private ImmutableArray _roles; public Guild Guild { get; } + public User GlobalUser { get; } /// public bool IsDeaf { get; private set; } @@ -23,18 +24,38 @@ namespace Discord.WebSocket /// public string Nickname { get; private set; } /// + public UserStatus Status { get; private set; } + /// + public Game? CurrentGame { get; private set; } + /// public VoiceChannel VoiceChannel { get; private set; } + /// public GuildPermissions GuildPermissions { get; private set; } /// public IReadOnlyList Roles => _roles; - internal override DiscordClient Discord => Guild.Discord; + /// + public string AvatarUrl => GlobalUser.AvatarUrl; + /// + public ushort Discriminator => GlobalUser.Discriminator; + /// + public bool IsBot => GlobalUser.IsBot; + /// + public string Username => GlobalUser.Username; + /// + public DateTime CreatedAt => GlobalUser.CreatedAt; + /// + public ulong Id => GlobalUser.Id; + /// + public string Mention => GlobalUser.Mention; + internal DiscordClient Discord => Guild.Discord; - internal GuildUser(Guild guild, Model model) - : base(model.User) + internal GuildUser(User globalUser, Guild guild, Model model) { + GlobalUser = globalUser; Guild = guild; + globalUser.Update(model.User); Update(model); } internal void Update(Model model) @@ -57,31 +78,6 @@ namespace Discord.WebSocket GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this)); } - public bool HasRole(IRole role) - { - for (int i = 0; i < _roles.Length; i++) - { - if (_roles[i].Id == role.Id) - return true; - } - return false; - } - - public async Task Kick() - { - await Discord.APIClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); - } - - public GuildPermissions GetGuildPermissions() - { - return new GuildPermissions(Permissions.ResolveGuild(this)); - } - public ChannelPermissions GetPermissions(IGuildChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); - } - public async Task Modify(Action func) { if (func == null) throw new NullReferenceException(nameof(func)); @@ -89,17 +85,17 @@ namespace Discord.WebSocket var args = new ModifyGuildMemberParams(); func(args); - bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; + bool isCurrentUser = Discord.CurrentUser.Id == Id; if (isCurrentUser && args.Nickname.IsSpecified) { var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; - await Discord.APIClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); + await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove } if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.Roles.IsSpecified) { - await Discord.APIClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + await Discord.ApiClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); if (args.Deaf.IsSpecified) IsDeaf = args.Deaf.Value; if (args.Mute.IsSpecified) @@ -111,6 +107,26 @@ namespace Discord.WebSocket } } + public async Task Kick() + { + await Discord.ApiClient.RemoveGuildMember(Guild.Id, Id).ConfigureAwait(false); + } + + public GuildPermissions GetGuildPermissions() + { + return new GuildPermissions(Permissions.ResolveGuild(this)); + } + public ChannelPermissions GetPermissions(IGuildChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return new ChannelPermissions(Permissions.ResolveChannel(this, channel, GuildPermissions.RawValue)); + } + + public async Task CreateDMChannel() + { + return await GlobalUser.CreateDMChannel().ConfigureAwait(false); + } + IGuild IGuildUser.Guild => Guild; IReadOnlyList IGuildUser.Roles => Roles; @@ -118,7 +134,10 @@ namespace Discord.WebSocket ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => GetPermissions(channel); + async Task IUser.CreateDMChannel() + => await CreateDMChannel().ConfigureAwait(false); Task IUpdateable.Update() => Task.CompletedTask; + } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs b/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs deleted file mode 100644 index f1816077e..000000000 --- a/src/Discord.Net/WebSocket/Entities/Users/PublicUser.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Model = Discord.API.User; - -namespace Discord.WebSocket -{ - public class PublicUser : User - { - internal override DiscordClient Discord { get; } - - internal PublicUser(DiscordClient discord, Model model) - : base(model) - { - Discord = discord; - } - } -} diff --git a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs index 9b97cf58e..8b8a86788 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/SelfUser.cs @@ -7,17 +7,14 @@ namespace Discord.WebSocket { public class SelfUser : User, ISelfUser { - internal override DiscordClient Discord { get; } - /// public string Email { get; private set; } /// public bool IsVerified { get; private set; } internal SelfUser(DiscordClient discord, Model model) - : base(model) + : base(discord, model) { - Discord = discord; } internal override void Update(Model model) { @@ -34,7 +31,7 @@ namespace Discord.WebSocket var args = new ModifyCurrentUserParams(); func(args); - await Discord.APIClient.ModifyCurrentUser(args).ConfigureAwait(false); + await Discord.ApiClient.ModifyCurrentUser(args).ConfigureAwait(false); } Task IUpdateable.Update() diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index f43465544..1101dd961 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -6,14 +6,15 @@ using Model = Discord.API.User; namespace Discord.WebSocket { + //TODO: Unload when there are no more references via DMUser or GuildUser [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class User : IUser + public class User : IUser { private string _avatarId; /// public ulong Id { get; } - internal abstract DiscordClient Discord { get; } + internal DiscordClient Discord { get; } /// public ushort Discriminator { get; private set; } @@ -21,6 +22,8 @@ namespace Discord.WebSocket public bool IsBot { get; private set; } /// public string Username { get; private set; } + /// + public DMChannel DMChannel { get; private set; } /// public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); @@ -31,8 +34,9 @@ namespace Discord.WebSocket /// public string NicknameMention => MentionUtils.Mention(this, true); - internal User(Model model) + internal User(DiscordClient discord, Model model) { + Discord = discord; Id = model.Id; Update(model); @@ -47,10 +51,16 @@ namespace Discord.WebSocket public async Task CreateDMChannel() { - var args = new CreateDMChannelParams { RecipientId = Id }; - var model = await Discord.APIClient.CreateDMChannel(args).ConfigureAwait(false); + var channel = DMChannel; + if (channel == null) + { + var args = new CreateDMChannelParams { RecipientId = Id }; + var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false); - return new DMChannel(Discord, model); + channel = new DMChannel(Discord, this, model); + DMChannel = channel; + } + return channel; } public override string ToString() => $"{Username}#{Discriminator}"; diff --git a/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs b/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs new file mode 100644 index 000000000..4689e44bc --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class ChannelEventArgs : EventArgs + { + public IChannel Channel { get; } + + public ChannelEventArgs(IChannel channel) + { + Channel = channel; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs new file mode 100644 index 000000000..24d001bc3 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class ChannelUpdatedEventArgs : ChannelEventArgs + { + public IChannel Before { get; } + public IChannel After => Channel; + + public ChannelUpdatedEventArgs(IChannel before, IChannel after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs b/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs new file mode 100644 index 000000000..97d352c31 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class CurrentUserEventArgs : EventArgs + { + public SelfUser CurrentUser { get; } + + public CurrentUserEventArgs(SelfUser currentUser) + { + CurrentUser = currentUser; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs new file mode 100644 index 000000000..167278089 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class CurrentUserUpdatedEventArgs : CurrentUserEventArgs + { + public SelfUser Before { get; } + public SelfUser After => CurrentUser; + + public CurrentUserUpdatedEventArgs(SelfUser before, SelfUser after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs b/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs new file mode 100644 index 000000000..d9120b243 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord.WebSocket +{ + public class DisconnectedEventArgs : EventArgs + { + public bool WasUnexpected { get; } + public Exception Exception { get; } + + public DisconnectedEventArgs(bool wasUnexpected, Exception exception = null) + { + WasUnexpected = wasUnexpected; + Exception = exception; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs b/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs new file mode 100644 index 000000000..8c9d4dc20 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class GuildEventArgs : EventArgs + { + public Guild Guild { get; } + + public GuildEventArgs(Guild guild) + { + Guild = guild; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs new file mode 100644 index 000000000..03a7f0f75 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class GuildUpdatedEventArgs : GuildEventArgs + { + public Guild Before { get; } + public Guild After => Guild; + + public GuildUpdatedEventArgs(Guild before, Guild after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs b/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs new file mode 100644 index 000000000..a1bea179a --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class MessageEventArgs : EventArgs + { + public Message Message { get; } + + public MessageEventArgs(Message message) + { + Message = message; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs new file mode 100644 index 000000000..14b41707d --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class MessageUpdatedEventArgs : MessageEventArgs + { + public Message Before { get; } + public Message After => Message; + + public MessageUpdatedEventArgs(Message before, Message after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs b/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs new file mode 100644 index 000000000..902eea1e8 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class RoleEventArgs : EventArgs + { + public Role Role { get; } + + public RoleEventArgs(Role role) + { + Role = role; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs new file mode 100644 index 000000000..45c6f05a9 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class RoleUpdatedEventArgs : RoleEventArgs + { + public Role Before { get; } + public Role After => Role; + + public RoleUpdatedEventArgs(Role before, Role after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs b/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs new file mode 100644 index 000000000..a3f45d452 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord.WebSocket +{ + public class TypingEventArgs : EventArgs + { + public IMessageChannel Channel { get; } + public IUser User { get; } + + public TypingEventArgs(IMessageChannel channel, IUser user) + { + Channel = channel; + User = user; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/UserEventArgs.cs b/src/Discord.Net/WebSocket/Events/UserEventArgs.cs new file mode 100644 index 000000000..8e31a58aa --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/UserEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class UserEventArgs : EventArgs + { + public IUser User { get; } + + public UserEventArgs(IUser user) + { + User = user; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs new file mode 100644 index 000000000..298b0d427 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.WebSocket +{ + public class UserUpdatedEventArgs : UserEventArgs + { + public IUser Before { get; } + public IUser After => User; + + public UserUpdatedEventArgs(IUser before, IUser after) + : base(after) + { + Before = before; + } + } +} diff --git a/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs b/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs new file mode 100644 index 000000000..1b10f3cd7 --- /dev/null +++ b/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace Discord.WebSocket +{ + public class VoiceChannelEventArgs : EventArgs + { + public VoiceChannel Channel { get; } + + public VoiceChannelEventArgs(VoiceChannel channel) + { + Channel = channel; + } + } +} diff --git a/src/Discord.Net/WebSocket/MessageCache.cs b/src/Discord.Net/WebSocket/MessageCache.cs index d81fb3498..5051efc3a 100644 --- a/src/Discord.Net/WebSocket/MessageCache.cs +++ b/src/Discord.Net/WebSocket/MessageCache.cs @@ -99,8 +99,9 @@ namespace Discord.WebSocket RelativeDirection = dir, RelativeMessageId = dir == Direction.Before ? cachedMessages[0].Id : cachedMessages[cachedMessages.Count - 1].Id }; - var downloadedMessages = await _discord.APIClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); - return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, x))).ToImmutableArray(); + var downloadedMessages = await _discord.ApiClient.GetChannelMessages(_channel.Id, args).ConfigureAwait(false); + //TODO: Ugly channel cast + return cachedMessages.AsEnumerable().Concat(downloadedMessages.Select(x => new Message(_channel, (_channel as Channel).GetUser(x.Id), x))).ToImmutableArray(); } } } diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index c47cb1411..947633661 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,14 +1,38 @@ -{ +{ + "version": "1.0.0-dev", + "description": "A Discord.Net extension adding voice support.", + "authors": [ "RogueException" ], + + "buildOptions": { + "allowUnsafe": true, + "warningsAsErrors": false + }, + + "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" + } + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", "Newtonsoft.Json": "8.0.3", - "System.Collections.Immutable": "1.1.37" + "System.Collections.Immutable": "1.2.0-rc2-24027", + "System.Net.Websockets.Client": "4.0.0-rc2-24027", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027" }, "frameworks": { - "net461": { } - }, - - "runtimes": { - "win": { } + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } } -} \ No newline at end of file +} From fdbe41d77f0d77d8ee07e12271c132973efdea6c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 19 May 2016 00:05:45 -0300 Subject: [PATCH 27/39] Fixed a couple Optional errors --- src/Discord.Net/API/Optional.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net/API/Optional.cs b/src/Discord.Net/API/Optional.cs index 0fe96071c..b828608b2 100644 --- a/src/Discord.Net/API/Optional.cs +++ b/src/Discord.Net/API/Optional.cs @@ -9,7 +9,7 @@ namespace Discord.API { private readonly T _value; - /// Gets the value for this paramter, or default(T) if unspecified. + /// Gets the value for this paramter. public T Value { get @@ -22,8 +22,6 @@ namespace Discord.API /// Returns true if this value has been specified. public bool IsSpecified { get; } - object IOptional.Value => _value; - /// Creates a new Parameter with the provided value. public Optional(T value) { @@ -40,12 +38,14 @@ namespace Discord.API if (other == null) return false; return _value.Equals(other); } - public override int GetHashCode() => IsSpecified ? _value.GetHashCode() : 0; + public override string ToString() => IsSpecified ? _value?.ToString() : null; private string DebuggerDisplay => IsSpecified ? _value.ToString() : ""; public static implicit operator Optional(T value) => new Optional(value); public static explicit operator T(Optional value) => value.Value; + + object IOptional.Value => Value; } } From 473b1bdbbcc54b714c6151da8f503937cefbd88a Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 19 May 2016 00:06:00 -0300 Subject: [PATCH 28/39] Fixed nicknames --- src/Discord.Net/Rest/Entities/Users/GuildUser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index c48aec4bf..33d100255 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -80,7 +80,7 @@ namespace Discord.Rest bool isCurrentUser = (await Discord.GetCurrentUser().ConfigureAwait(false)).Id == Id; if (isCurrentUser && args.Nickname.IsSpecified) { - var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value }; + var nickArgs = new ModifyCurrentUserNickParams { Nickname = args.Nickname.Value ?? "" }; await Discord.ApiClient.ModifyCurrentUserNick(Guild.Id, nickArgs).ConfigureAwait(false); args.Nickname = new API.Optional(); //Remove } @@ -93,7 +93,7 @@ namespace Discord.Rest if (args.Mute.IsSpecified) IsMute = args.Mute.Value; if (args.Nickname.IsSpecified) - Nickname = args.Nickname.Value; + Nickname = args.Nickname.Value ?? ""; if (args.Roles.IsSpecified) _roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); } From 4aba51f2fdb7b65c8c76c9e30b62aa90207df476 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 19 May 2016 00:06:12 -0300 Subject: [PATCH 29/39] Added Game(name) constructor --- src/Discord.Net/Common/Entities/Users/Game.cs | 6 ++++++ src/Discord.Net/project.json | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net/Common/Entities/Users/Game.cs b/src/Discord.Net/Common/Entities/Users/Game.cs index 51d9c0b18..ee5559165 100644 --- a/src/Discord.Net/Common/Entities/Users/Game.cs +++ b/src/Discord.Net/Common/Entities/Users/Game.cs @@ -6,6 +6,12 @@ public string StreamUrl { get; } public StreamType StreamType { get; } + public Game(string name) + { + Name = name; + StreamUrl = null; + StreamType = StreamType.NotStreaming; + } public Game(string name, string streamUrl, StreamType type) { Name = name; diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 947633661..01b6bd4dc 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -3,11 +3,6 @@ "description": "A Discord.Net extension adding voice support.", "authors": [ "RogueException" ], - "buildOptions": { - "allowUnsafe": true, - "warningsAsErrors": false - }, - "packOptions": { "tags": [ "discord", "discordapp" ], "licenseUrl": "http://opensource.org/licenses/MIT", @@ -18,6 +13,11 @@ } }, + "buildOptions": { + "allowUnsafe": true, + "warningsAsErrors": false + }, + "dependencies": { "NETStandard.Library": "1.5.0-rc2-24027", "Newtonsoft.Json": "8.0.3", From c6c18bcf47c4f8b8ad009b8c4b41e7bf3613b84d Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 19 May 2016 10:04:59 -0300 Subject: [PATCH 30/39] Added Voice Channel user_limit --- src/Discord.Net/API/Common/Channel.cs | 6 ++++++ src/Discord.Net/API/DiscordAPIClient.cs | 1 + src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs | 2 ++ src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/Discord.Net/API/Common/Channel.cs b/src/Discord.Net/API/Common/Channel.cs index e724f1285..f50c31a09 100644 --- a/src/Discord.Net/API/Common/Channel.cs +++ b/src/Discord.Net/API/Common/Channel.cs @@ -23,10 +23,16 @@ namespace Discord.API public int Position { get; set; } [JsonProperty("permission_overwrites")] public Overwrite[] PermissionOverwrites { get; set; } + + //TextChannel [JsonProperty("topic")] public string Topic { get; set; } + + //VoiceChannel [JsonProperty("bitrate")] public int Bitrate { get; set; } + [JsonProperty("user_limit")] + public int UserLimit { get; set; } //DMChannel [JsonProperty("recipient")] diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index bde034256..c634ea3f7 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -235,6 +235,7 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Bitrate, 0, nameof(args.Bitrate)); + Preconditions.AtLeast(args.UserLimit, 0, nameof(args.Bitrate)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); diff --git a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs index b268783c8..8d449c607 100644 --- a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs @@ -6,5 +6,7 @@ namespace Discord.API.Rest { [JsonProperty("bitrate")] public Optional Bitrate { get; set; } + [JsonProperty("user_limit")] + public Optional UserLimit { get; set; } } } diff --git a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs index 208e31e49..e105aabd6 100644 --- a/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/Rest/Entities/Channels/VoiceChannel.cs @@ -12,6 +12,8 @@ namespace Discord.Rest { /// public int Bitrate { get; private set; } + /// + public int UserLimit { get; private set; } internal VoiceChannel(Guild guild, Model model) : base(guild, model) @@ -21,6 +23,7 @@ namespace Discord.Rest { base.Update(model); Bitrate = model.Bitrate; + UserLimit = model.UserLimit; } /// From af680b7f43f9de600c06a8b42bd8edaa63ef0399 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 19 May 2016 10:09:23 -0300 Subject: [PATCH 31/39] Added UserLimit to IVoiceChannel --- src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs | 2 ++ src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs index baa2d741f..d94a97a63 100644 --- a/src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs @@ -8,6 +8,8 @@ namespace Discord { /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. int Bitrate { get; } + /// Gets the max amount of users allowed to be connected to this channel at one time. A value of 0 represents no limit. + int UserLimit { get; } /// Modifies this voice channel. Task Modify(Action func); diff --git a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs index eec317352..d1f374499 100644 --- a/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs +++ b/src/Discord.Net/WebSocket/Entities/Channels/VoiceChannel.cs @@ -13,6 +13,8 @@ namespace Discord.WebSocket { /// public int Bitrate { get; private set; } + /// + public int UserLimit { get; private set; } public override IEnumerable Users => Guild.Users.Where(x => x.VoiceChannel == this); @@ -25,6 +27,7 @@ namespace Discord.WebSocket { base.Update(model); Bitrate = model.Bitrate; + UserLimit = model.UserLimit; } /// From 50392d3688c815fb296c137e9c895c6fa2d4cc28 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 23 May 2016 02:22:51 -0300 Subject: [PATCH 32/39] Added a couple of buckets, fixed logging out --- src/Discord.Net/API/DiscordAPIClient.cs | 21 ++++++++++++++----- .../Net/Rest/RequestQueue/GlobalBucket.cs | 1 + .../Net/Rest/RequestQueue/GuildBucket.cs | 1 + .../Net/Rest/RequestQueue/RequestQueue.cs | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index c634ea3f7..edfdb9a64 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -23,7 +23,7 @@ namespace Discord.API private readonly RequestQueue _requestQueue; private readonly JsonSerializer _serializer; - private IRestClient _restClient; + private readonly IRestClient _restClient; private CancellationToken _cancelToken; public TokenType AuthTokenType { get; private set; } @@ -68,15 +68,26 @@ namespace Discord.API } public async Task Login(LoginParams args, CancellationToken cancelToken) { - var response = await Send("POST", "auth/login", args).ConfigureAwait(false); - AuthTokenType = TokenType.User; + _restClient.SetHeader("authorization", null); + _cancelToken = cancelToken; + + LoginResponse response; + try + { + response = await Send("POST", "auth/login", args, GlobalBucket.Login).ConfigureAwait(false); + } + catch + { + _cancelToken = CancellationToken.None; + throw; + } + _restClient.SetHeader("authorization", response.Token); } public async Task Logout() { await _requestQueue.Clear().ConfigureAwait(false); - _restClient = null; } //Core @@ -527,7 +538,7 @@ namespace Discord.API Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); - await Send("PATCH", $"guilds/{guildId}/members/{userId}", args).ConfigureAwait(false); + await Send("PATCH", $"guilds/{guildId}/members/{userId}", args, GuildBucket.ModifyMember, guildId).ConfigureAwait(false); } //Guild Roles diff --git a/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs b/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs index 4e7126f5e..9ce523155 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs @@ -3,6 +3,7 @@ public enum GlobalBucket { General, + Login, DirectMessage } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs b/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs index ccb3fa994..cb4c0d9a8 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs @@ -5,6 +5,7 @@ SendEditMessage, DeleteMessage, DeleteMessages, + ModifyMember, Nickname } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs index 39a657cb4..9bf2189fe 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs @@ -64,6 +64,7 @@ namespace Discord.Net.Rest { //Globals case GlobalBucket.General: return new RequestQueueBucket(this, bucket, int.MaxValue, 0); //Catch-all + case GlobalBucket.Login: return new RequestQueueBucket(this, bucket, 1, 1); //TODO: Is this actual logins or token validations too? case GlobalBucket.DirectMessage: return new RequestQueueBucket(this, bucket, 5, 5); default: throw new ArgumentException($"Unknown global bucket: {bucket}", nameof(bucket)); @@ -77,6 +78,7 @@ namespace Discord.Net.Rest case GuildBucket.SendEditMessage: return new RequestQueueBucket(this, bucket, guildId, 5, 5); case GuildBucket.DeleteMessage: return new RequestQueueBucket(this, bucket, guildId, 5, 1); case GuildBucket.DeleteMessages: return new RequestQueueBucket(this, bucket, guildId, 1, 1); + case GuildBucket.ModifyMember: return new RequestQueueBucket(this, bucket, guildId, 10, 10); //TODO: Is this all users or just roles? case GuildBucket.Nickname: return new RequestQueueBucket(this, bucket, guildId, 1, 1); default: throw new ArgumentException($"Unknown guild bucket: {bucket}", nameof(bucket)); From b93abcc95ba86a08d897da38f26b778cc7165a3a Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 01:52:11 -0300 Subject: [PATCH 33/39] Added initial websocket support --- src/Discord.Net/API/DiscordAPIClient.cs | 215 ++++- src/Discord.Net/API/DiscordAPISocketClient.cs | 11 + .../API/Gateway/GuildMembersChunkEvent.cs | 12 + .../API/Gateway/GuildRoleCreateEvent.cs | 12 + .../API/Gateway/GuildRoleUpdateEvent.cs | 12 + src/Discord.Net/API/Gateway/IdentifyParams.cs | 17 + src/Discord.Net/API/Gateway/OpCodes.cs | 24 + src/Discord.Net/API/Gateway/ReadyEvent.cs | 40 + .../API/Gateway/RequestMembersParams.cs | 14 + src/Discord.Net/API/Gateway/ResumeParams.cs | 12 + src/Discord.Net/API/Gateway/ResumedEvent.cs | 10 + .../API/Gateway/TypingStartEvent.cs | 14 + .../API/Gateway/UpdateStatusParams.cs | 12 + .../API/Gateway/UpdateVoiceParams.cs | 16 + .../API/Gateway/VoiceServerUpdateEvent.cs | 14 + src/Discord.Net/API/IWebSocketMessage.cs | 9 - src/Discord.Net/API/WebSocketMessage.cs | 10 +- src/Discord.Net/Common/EventExtensions.cs | 13 - src/Discord.Net/ConnectionState.cs | 10 + src/Discord.Net/{Common => }/DateTimeUtils.cs | 3 +- .../Entities/Channels/ChannelType.cs | 0 .../Entities/Channels/IChannel.cs | 0 .../Entities/Channels/IDMChannel.cs | 0 .../Entities/Channels/IGuildChannel.cs | 0 .../Entities/Channels/IMessageChannel.cs | 0 .../Entities/Channels/ITextChannel.cs | 0 .../Entities/Channels/IVoiceChannel.cs | 0 .../{Common => }/Entities/Guilds/Emoji.cs | 0 .../{Common => }/Entities/Guilds/IGuild.cs | 0 .../Entities/Guilds/IGuildEmbed.cs | 0 .../Entities/Guilds/IGuildIntegration.cs | 0 .../Entities/Guilds/IUserGuild.cs | 0 .../Entities/Guilds/IVoiceRegion.cs | 0 .../Entities/Guilds/IntegrationAccount.cs | 0 .../Entities/Guilds/VoiceRegion.cs | 0 .../{Common => }/Entities/IDeletable.cs | 0 .../{Common => }/Entities/IEntity.cs | 0 .../{Common => }/Entities/IMentionable.cs | 0 .../{Common => }/Entities/ISnowflakeEntity.cs | 0 .../{Common => }/Entities/IUpdateable.cs | 0 .../{Common => }/Entities/Invites/IInvite.cs | 0 .../Entities/Invites/IInviteMetadata.cs | 0 .../{Common => }/Entities/Invites/Invite.cs | 0 .../Entities/Invites/InviteMetadata.cs | 0 .../Entities/Messages/Attachment.cs | 0 .../Entities/Messages/Direction.cs | 0 .../{Common => }/Entities/Messages/Embed.cs | 0 .../Entities/Messages/EmbedProvider.cs | 0 .../Entities/Messages/EmbedThumbnail.cs | 0 .../Entities/Messages/IMessage.cs | 0 .../Entities/Permissions/ChannelPermission.cs | 0 .../Permissions/ChannelPermissions.cs | 0 .../Entities/Permissions/GuildPermission.cs | 0 .../Entities/Permissions/GuildPermissions.cs | 0 .../Entities/Permissions/Overwrite.cs | 0 .../Permissions/OverwritePermissions.cs | 0 .../Entities/Permissions/PermValue.cs | 0 .../Entities/Permissions/PermissionTarget.cs | 0 .../Entities/Permissions/Permissions.cs | 0 .../{Common => }/Entities/Roles/Color.cs | 0 .../{Common => }/Entities/Roles/IRole.cs | 0 .../{Common => }/Entities/Users/Connection.cs | 0 .../{Common => }/Entities/Users/Game.cs | 0 .../Entities/Users/IConnection.cs | 0 .../{Common => }/Entities/Users/IGuildUser.cs | 0 .../{Common => }/Entities/Users/ISelfUser.cs | 0 .../{Common => }/Entities/Users/IUser.cs | 0 .../Entities/Users/IVoiceState.cs.old | 0 .../{Common => }/Entities/Users/StreamType.cs | 0 .../{Common => }/Entities/Users/UserStatus.cs | 0 src/Discord.Net/EventExtensions.cs | 45 + .../Events/LogMessageEventArgs.cs | 0 .../Events/SentRequestEventArgs.cs | 0 src/Discord.Net/IDiscordClient.cs | 12 +- src/Discord.Net/Logging/ILogger.cs | 37 +- src/Discord.Net/Logging/LogManager.cs | 87 +- src/Discord.Net/LoginState.cs | 10 + src/Discord.Net/{Common => }/MentionUtils.cs | 0 .../RequestQueue => Queue}/BucketGroup.cs | 2 +- src/Discord.Net/Net/Queue/GlobalBucket.cs | 12 + .../RequestQueue => Queue}/GuildBucket.cs | 2 +- src/Discord.Net/Net/Queue/IQueuedRequest.cs | 13 + .../RequestQueue => Queue}/IRequestQueue.cs | 2 +- .../RequestQueue => Queue}/RequestQueue.cs | 17 +- .../RequestQueueBucket.cs | 31 +- src/Discord.Net/Net/Queue/RestRequest.cs | 53 ++ src/Discord.Net/Net/Queue/WebSocketRequest.cs | 34 + src/Discord.Net/Net/Rest/DefaultRestClient.cs | 29 +- src/Discord.Net/Net/Rest/IRestClient.cs | 6 +- .../Net/Rest/RequestQueue/GlobalBucket.cs | 9 - .../Net/Rest/RequestQueue/RestRequest.cs | 42 - .../Net/WebSockets/DefaultWebsocketClient.cs | 183 ++-- .../Net/WebSockets/IWebSocketClient.cs | 10 +- .../Net/WebSockets/WebSocketProvider.cs | 2 +- src/Discord.Net/Rest/DiscordClient.cs | 119 ++- .../WebSocket/Data/DataStoreProvider.cs | 4 + .../WebSocket/Data/DefaultDataStore.cs | 110 +++ src/Discord.Net/WebSocket/Data/IDataStore.cs | 28 + .../WebSocket/Data/SharedDataStore.cs | 7 + src/Discord.Net/WebSocket/DiscordClient.cs | 863 +++++++++++++++--- .../WebSocket/DiscordSocketConfig.cs | 7 +- .../WebSocket/Entities/Users/User.cs | 3 +- .../WebSocket/Events/ChannelEventArgs.cs | 14 - .../Events/ChannelUpdatedEventArgs.cs | 14 - .../WebSocket/Events/CurrentUserEventArgs.cs | 14 - .../Events/CurrentUserUpdatedEventArgs.cs | 14 - .../WebSocket/Events/DisconnectedEventArgs.cs | 16 - .../WebSocket/Events/GuildEventArgs.cs | 14 - .../WebSocket/Events/GuildUpdatedEventArgs.cs | 14 - .../WebSocket/Events/MessageEventArgs.cs | 14 - .../Events/MessageUpdatedEventArgs.cs | 14 - .../WebSocket/Events/RoleEventArgs.cs | 14 - .../WebSocket/Events/RoleUpdatedEventArgs.cs | 14 - .../WebSocket/Events/TypingEventArgs.cs | 16 - .../WebSocket/Events/UserEventArgs.cs | 14 - .../WebSocket/Events/UserUpdatedEventArgs.cs | 14 - .../WebSocket/Events/VoiceChannelEventArgs.cs | 14 - src/Discord.Net/project.json | 4 +- 118 files changed, 1713 insertions(+), 764 deletions(-) create mode 100644 src/Discord.Net/API/DiscordAPISocketClient.cs create mode 100644 src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs create mode 100644 src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs create mode 100644 src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs create mode 100644 src/Discord.Net/API/Gateway/IdentifyParams.cs create mode 100644 src/Discord.Net/API/Gateway/OpCodes.cs create mode 100644 src/Discord.Net/API/Gateway/ReadyEvent.cs create mode 100644 src/Discord.Net/API/Gateway/RequestMembersParams.cs create mode 100644 src/Discord.Net/API/Gateway/ResumeParams.cs create mode 100644 src/Discord.Net/API/Gateway/ResumedEvent.cs create mode 100644 src/Discord.Net/API/Gateway/TypingStartEvent.cs create mode 100644 src/Discord.Net/API/Gateway/UpdateStatusParams.cs create mode 100644 src/Discord.Net/API/Gateway/UpdateVoiceParams.cs create mode 100644 src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs delete mode 100644 src/Discord.Net/API/IWebSocketMessage.cs delete mode 100644 src/Discord.Net/Common/EventExtensions.cs create mode 100644 src/Discord.Net/ConnectionState.cs rename src/Discord.Net/{Common => }/DateTimeUtils.cs (79%) rename src/Discord.Net/{Common => }/Entities/Channels/ChannelType.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/IChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/IDMChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/IGuildChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/IMessageChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/ITextChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Channels/IVoiceChannel.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/Emoji.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IGuild.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IGuildEmbed.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IGuildIntegration.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IUserGuild.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IVoiceRegion.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/IntegrationAccount.cs (100%) rename src/Discord.Net/{Common => }/Entities/Guilds/VoiceRegion.cs (100%) rename src/Discord.Net/{Common => }/Entities/IDeletable.cs (100%) rename src/Discord.Net/{Common => }/Entities/IEntity.cs (100%) rename src/Discord.Net/{Common => }/Entities/IMentionable.cs (100%) rename src/Discord.Net/{Common => }/Entities/ISnowflakeEntity.cs (100%) rename src/Discord.Net/{Common => }/Entities/IUpdateable.cs (100%) rename src/Discord.Net/{Common => }/Entities/Invites/IInvite.cs (100%) rename src/Discord.Net/{Common => }/Entities/Invites/IInviteMetadata.cs (100%) rename src/Discord.Net/{Common => }/Entities/Invites/Invite.cs (100%) rename src/Discord.Net/{Common => }/Entities/Invites/InviteMetadata.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/Attachment.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/Direction.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/Embed.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/EmbedProvider.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/EmbedThumbnail.cs (100%) rename src/Discord.Net/{Common => }/Entities/Messages/IMessage.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/ChannelPermission.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/ChannelPermissions.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/GuildPermission.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/GuildPermissions.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/Overwrite.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/OverwritePermissions.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/PermValue.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/PermissionTarget.cs (100%) rename src/Discord.Net/{Common => }/Entities/Permissions/Permissions.cs (100%) rename src/Discord.Net/{Common => }/Entities/Roles/Color.cs (100%) rename src/Discord.Net/{Common => }/Entities/Roles/IRole.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/Connection.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/Game.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/IConnection.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/IGuildUser.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/ISelfUser.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/IUser.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/IVoiceState.cs.old (100%) rename src/Discord.Net/{Common => }/Entities/Users/StreamType.cs (100%) rename src/Discord.Net/{Common => }/Entities/Users/UserStatus.cs (100%) create mode 100644 src/Discord.Net/EventExtensions.cs rename src/Discord.Net/{Common => }/Events/LogMessageEventArgs.cs (100%) rename src/Discord.Net/{Common => }/Events/SentRequestEventArgs.cs (100%) create mode 100644 src/Discord.Net/LoginState.cs rename src/Discord.Net/{Common => }/MentionUtils.cs (100%) rename src/Discord.Net/Net/{Rest/RequestQueue => Queue}/BucketGroup.cs (71%) create mode 100644 src/Discord.Net/Net/Queue/GlobalBucket.cs rename src/Discord.Net/Net/{Rest/RequestQueue => Queue}/GuildBucket.cs (83%) create mode 100644 src/Discord.Net/Net/Queue/IQueuedRequest.cs rename src/Discord.Net/Net/{Rest/RequestQueue => Queue}/IRequestQueue.cs (87%) rename src/Discord.Net/Net/{Rest/RequestQueue => Queue}/RequestQueue.cs (92%) rename src/Discord.Net/Net/{Rest/RequestQueue => Queue}/RequestQueueBucket.cs (86%) create mode 100644 src/Discord.Net/Net/Queue/RestRequest.cs create mode 100644 src/Discord.Net/Net/Queue/WebSocketRequest.cs delete mode 100644 src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs delete mode 100644 src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs create mode 100644 src/Discord.Net/WebSocket/Data/DataStoreProvider.cs create mode 100644 src/Discord.Net/WebSocket/Data/DefaultDataStore.cs create mode 100644 src/Discord.Net/WebSocket/Data/IDataStore.cs create mode 100644 src/Discord.Net/WebSocket/Data/SharedDataStore.cs delete mode 100644 src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/GuildEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/MessageEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/RoleEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/TypingEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/UserEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs delete mode 100644 src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index edfdb9a64..2f285030d 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -1,7 +1,9 @@ using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; +using Discord.Net.Queue; using Discord.Net.Rest; +using Discord.Net.WebSockets; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -17,77 +19,202 @@ using System.Threading.Tasks; namespace Discord.API { - public class DiscordApiClient + public class DiscordApiClient : IDisposable { - internal event EventHandler SentRequest; + internal event Func SentRequest; private readonly RequestQueue _requestQueue; private readonly JsonSerializer _serializer; private readonly IRestClient _restClient; - private CancellationToken _cancelToken; + private readonly IWebSocketClient _gatewayClient; + private readonly SemaphoreSlim _connectionLock; + private CancellationTokenSource _loginCancelToken, _connectCancelToken; + private bool _isDisposed; + public LoginState LoginState { get; private set; } + public ConnectionState ConnectionState { get; private set; } public TokenType AuthTokenType { get; private set; } - public IRestClient RestClient { get; private set; } - public IRequestQueue RequestQueue { get; private set; } - public DiscordApiClient(RestClientProvider restClientProvider) + public DiscordApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider = null, JsonSerializer serializer = null, RequestQueue requestQueue = null) { + _connectionLock = new SemaphoreSlim(1, 1); + + _requestQueue = requestQueue ?? new RequestQueue(); + _restClient = restClientProvider(DiscordConfig.ClientAPIUrl); _restClient.SetHeader("accept", "*/*"); _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); + if (webSocketProvider != null) + { + _gatewayClient = webSocketProvider(); + _gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); + } - _requestQueue = new RequestQueue(_restClient); - - _serializer = new JsonSerializer() + _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + } + void Dispose(bool disposing) + { + if (!_isDisposed) { - ContractResolver = new DiscordContractResolver() - }; + if (disposing) + { + _loginCancelToken?.Dispose(); + _connectCancelToken?.Dispose(); + } + _isDisposed = true; + } } + public void Dispose() => Dispose(true); - public async Task Login(TokenType tokenType, string token, CancellationToken cancelToken) + public async Task Login(LoginParams args) + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LoginInternal(TokenType.User, null, args, true).ConfigureAwait(false); + } + finally { _connectionLock.Release(); } + } + public async Task Login(TokenType tokenType, string token) + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LoginInternal(tokenType, token, null, false).ConfigureAwait(false); + } + finally { _connectionLock.Release(); } + } + private async Task LoginInternal(TokenType tokenType, string token, LoginParams args, bool doLogin) { - AuthTokenType = tokenType; - _cancelToken = cancelToken; - await _requestQueue.SetCancelToken(cancelToken).ConfigureAwait(false); + if (LoginState != LoginState.LoggedOut) + await LogoutInternal().ConfigureAwait(false); + LoginState = LoginState.LoggingIn; - switch (tokenType) + try { - case TokenType.Bot: - token = $"Bot {token}"; - break; - case TokenType.Bearer: - token = $"Bearer {token}"; - break; - case TokenType.User: - break; - default: - throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); + _loginCancelToken = new CancellationTokenSource(); + + AuthTokenType = TokenType.User; + _restClient.SetHeader("authorization", null); + await _requestQueue.SetCancelToken(_loginCancelToken.Token).ConfigureAwait(false); + _restClient.SetCancelToken(_loginCancelToken.Token); + + if (doLogin) + { + var response = await Send("POST", "auth/login", args, GlobalBucket.Login).ConfigureAwait(false); + token = response.Token; + } + + AuthTokenType = tokenType; + switch (tokenType) + { + case TokenType.Bot: + token = $"Bot {token}"; + break; + case TokenType.Bearer: + token = $"Bearer {token}"; + break; + case TokenType.User: + break; + default: + throw new ArgumentException("Unknown oauth token type", nameof(tokenType)); + } + _restClient.SetHeader("authorization", token); + + LoginState = LoginState.LoggedIn; + } + catch (Exception) + { + await LogoutInternal().ConfigureAwait(false); + throw; + } + } + + public async Task Logout() + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await LogoutInternal().ConfigureAwait(false); } + finally { _connectionLock.Release(); } + } + private async Task LogoutInternal() + { + //TODO: An exception here will lock the client into the unusable LoggingOut state. How should we handle? (Add same solution to both DiscordClients too) + if (LoginState == LoginState.LoggedOut) return; + LoginState = LoginState.LoggingOut; + + try { _loginCancelToken?.Cancel(false); } + catch { } + + await DisconnectInternal().ConfigureAwait(false); + await _requestQueue.Clear().ConfigureAwait(false); - _restClient.SetHeader("authorization", token); + await _requestQueue.SetCancelToken(CancellationToken.None).ConfigureAwait(false); + _restClient.SetCancelToken(CancellationToken.None); + + LoginState = LoginState.LoggedOut; + } + + public async Task Connect() + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await ConnectInternal().ConfigureAwait(false); + } + finally { _connectionLock.Release(); } } - public async Task Login(LoginParams args, CancellationToken cancelToken) + private async Task ConnectInternal() { - AuthTokenType = TokenType.User; - _restClient.SetHeader("authorization", null); - _cancelToken = cancelToken; + if (LoginState != LoginState.LoggedIn) + throw new InvalidOperationException("You must log in before connecting."); + if (_gatewayClient == null) + throw new NotSupportedException("This client is not configured with websocket support."); - LoginResponse response; + ConnectionState = ConnectionState.Connecting; try { - response = await Send("POST", "auth/login", args, GlobalBucket.Login).ConfigureAwait(false); + _connectCancelToken = new CancellationTokenSource(); + if (_gatewayClient != null) + _gatewayClient.SetCancelToken(_connectCancelToken.Token); + + var gatewayResponse = await GetGateway().ConfigureAwait(false); + await _gatewayClient.Connect(gatewayResponse.Url).ConfigureAwait(false); + + ConnectionState = ConnectionState.Connected; } - catch + catch (Exception) { - _cancelToken = CancellationToken.None; + await DisconnectInternal().ConfigureAwait(false); throw; } + } - _restClient.SetHeader("authorization", response.Token); + public async Task Disconnect() + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectInternal().ConfigureAwait(false); + } + finally { _connectionLock.Release(); } } - public async Task Logout() + private async Task DisconnectInternal() { - await _requestQueue.Clear().ConfigureAwait(false); + if (_gatewayClient == null) + throw new NotSupportedException("This client is not configured with websocket support."); + + if (ConnectionState == ConnectionState.Disconnected) return; + ConnectionState = ConnectionState.Disconnecting; + + try { _connectCancelToken?.Cancel(false); } + catch { } + + await _gatewayClient.Disconnect().ConfigureAwait(false); + + ConnectionState = ConnectionState.Disconnected; } //Core @@ -134,32 +261,28 @@ namespace Discord.API private async Task SendInternal(string method, string endpoint, object payload, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { - _cancelToken.ThrowIfCancellationRequested(); - var stopwatch = Stopwatch.StartNew(); string json = null; if (payload != null) json = Serialize(payload); - var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, json, headerOnly), group, bucketId, guildId).ConfigureAwait(false); + var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, json, headerOnly), group, bucketId, guildId).ConfigureAwait(false); int bytes = headerOnly ? 0 : (int)responseStream.Length; stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); + await SentRequest.Raise(new SentRequestEventArgs(method, endpoint, bytes, milliseconds)).ConfigureAwait(false); return responseStream; } private async Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { - _cancelToken.ThrowIfCancellationRequested(); - var stopwatch = Stopwatch.StartNew(); - var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, multipartArgs, headerOnly), group, bucketId, guildId).ConfigureAwait(false); + var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, multipartArgs, headerOnly), group, bucketId, guildId).ConfigureAwait(false); int bytes = headerOnly ? 0 : (int)responseStream.Length; stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - SentRequest?.Invoke(this, new SentRequestEventArgs(method, endpoint, bytes, milliseconds)); + await SentRequest.Raise(new SentRequestEventArgs(method, endpoint, bytes, milliseconds)).ConfigureAwait(false); return responseStream; } diff --git a/src/Discord.Net/API/DiscordAPISocketClient.cs b/src/Discord.Net/API/DiscordAPISocketClient.cs new file mode 100644 index 000000000..fcff89923 --- /dev/null +++ b/src/Discord.Net/API/DiscordAPISocketClient.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.API +{ + public class DiscordAPISocketClient + { + } +} diff --git a/src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs b/src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs new file mode 100644 index 000000000..d6402731a --- /dev/null +++ b/src/Discord.Net/API/Gateway/GuildMembersChunkEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class GuildMembersChunkEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("members")] + public GuildMember[] Members { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs b/src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs new file mode 100644 index 000000000..f05543bf6 --- /dev/null +++ b/src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class GuildRoleCreateEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("role")] + public Role Data { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs b/src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs new file mode 100644 index 000000000..345154432 --- /dev/null +++ b/src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class GuildRoleUpdateEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("role")] + public Role Data { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/IdentifyParams.cs b/src/Discord.Net/API/Gateway/IdentifyParams.cs new file mode 100644 index 000000000..96c45927a --- /dev/null +++ b/src/Discord.Net/API/Gateway/IdentifyParams.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API.Gateway +{ + public class IdentifyCommand + { + [JsonProperty("token")] + public string Token { get; set; } + [JsonProperty("properties")] + public IDictionary Properties { get; set; } + [JsonProperty("large_threshold")] + public int LargeThreshold { get; set; } + [JsonProperty("compress")] + public bool UseCompression { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/OpCodes.cs b/src/Discord.Net/API/Gateway/OpCodes.cs new file mode 100644 index 000000000..cf237bd03 --- /dev/null +++ b/src/Discord.Net/API/Gateway/OpCodes.cs @@ -0,0 +1,24 @@ +namespace Discord.API.Gateway +{ + public enum OpCodes : byte + { + /// C←S - Used to send most events. + Dispatch = 0, + /// C↔S - Used to keep the connection alive and measure latency. + Heartbeat = 1, + /// C→S - Used to associate a connection with a token and specify configuration. + Identify = 2, + /// C→S - Used to update client's status and current game id. + StatusUpdate = 3, + /// C→S - Used to join a particular voice channel. + VoiceStateUpdate = 4, + /// C→S - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops. + VoiceServerPing = 5, + /// C→S - Used to resume a connection after a redirect occurs. + Resume = 6, + /// C←S - Used to notify a client that they must reconnect to another gateway. + Reconnect = 7, + /// C→S - Used to request all members that were withheld by large_threshold + RequestGuildMembers = 8 + } +} diff --git a/src/Discord.Net/API/Gateway/ReadyEvent.cs b/src/Discord.Net/API/Gateway/ReadyEvent.cs new file mode 100644 index 000000000..c0384c100 --- /dev/null +++ b/src/Discord.Net/API/Gateway/ReadyEvent.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class ReadyEvent + { + public class ReadState + { + [JsonProperty("id")] + public string ChannelId { get; set; } + [JsonProperty("mention_count")] + public int MentionCount { get; set; } + [JsonProperty("last_message_id")] + public string LastMessageId { get; set; } + } + + [JsonProperty("v")] + public int Version { get; set; } + [JsonProperty("user")] + public User User { get; set; } + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("read_state")] + public ReadState[] ReadStates { get; set; } + [JsonProperty("guilds")] + public Guild[] Guilds { get; set; } + [JsonProperty("private_channels")] + public Channel[] PrivateChannels { get; set; } + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + + //Ignored + [JsonProperty("user_settings")] + public object UserSettings { get; set; } + [JsonProperty("user_guild_settings")] + public object UserGuildSettings { get; set; } + [JsonProperty("tutorial")] + public object Tutorial { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/RequestMembersParams.cs b/src/Discord.Net/API/Gateway/RequestMembersParams.cs new file mode 100644 index 000000000..16077939d --- /dev/null +++ b/src/Discord.Net/API/Gateway/RequestMembersParams.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class RequestMembersCommand + { + [JsonProperty("guild_id")] + public ulong[] GuildId { get; set; } + [JsonProperty("query")] + public string Query { get; set; } + [JsonProperty("limit")] + public int Limit { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/ResumeParams.cs b/src/Discord.Net/API/Gateway/ResumeParams.cs new file mode 100644 index 000000000..c0fb296f4 --- /dev/null +++ b/src/Discord.Net/API/Gateway/ResumeParams.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class ResumeCommand + { + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("seq")] + public uint Sequence { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/ResumedEvent.cs b/src/Discord.Net/API/Gateway/ResumedEvent.cs new file mode 100644 index 000000000..6087a0c38 --- /dev/null +++ b/src/Discord.Net/API/Gateway/ResumedEvent.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class ResumedEvent + { + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/TypingStartEvent.cs b/src/Discord.Net/API/Gateway/TypingStartEvent.cs new file mode 100644 index 000000000..2e3829bc7 --- /dev/null +++ b/src/Discord.Net/API/Gateway/TypingStartEvent.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class TypingStartEvent + { + [JsonProperty("user_id")] + public ulong UserId { get; set; } + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + [JsonProperty("timestamp")] + public int Timestamp { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/UpdateStatusParams.cs b/src/Discord.Net/API/Gateway/UpdateStatusParams.cs new file mode 100644 index 000000000..e673cb51e --- /dev/null +++ b/src/Discord.Net/API/Gateway/UpdateStatusParams.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class UpdateStatusCommand + { + [JsonProperty("idle_since")] + public long? IdleSince { get; set; } + [JsonProperty("game")] + public Game Game { get; set; } + } +} diff --git a/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs b/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs new file mode 100644 index 000000000..689394e5d --- /dev/null +++ b/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class UpdateVoiceCommand + { + [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; } + } +} diff --git a/src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs new file mode 100644 index 000000000..036d535c2 --- /dev/null +++ b/src/Discord.Net/API/Gateway/VoiceServerUpdateEvent.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + public class VoiceServerUpdateEvent + { + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("endpoint")] + public string Endpoint { get; set; } + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/src/Discord.Net/API/IWebSocketMessage.cs b/src/Discord.Net/API/IWebSocketMessage.cs deleted file mode 100644 index 526f3119f..000000000 --- a/src/Discord.Net/API/IWebSocketMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord.API -{ - public interface IWebSocketMessage - { - int OpCode { get; } - object Payload { get; } - bool IsPrivate { get; } - } -} diff --git a/src/Discord.Net/API/WebSocketMessage.cs b/src/Discord.Net/API/WebSocketMessage.cs index 05d5e779d..285f5e13f 100644 --- a/src/Discord.Net/API/WebSocketMessage.cs +++ b/src/Discord.Net/API/WebSocketMessage.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Discord.API { @@ -11,13 +12,6 @@ namespace Discord.API [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] public uint? Sequence { get; set; } [JsonProperty("d")] - public object Payload { get; set; } - - public WebSocketMessage() { } - public WebSocketMessage(IWebSocketMessage msg) - { - Operation = msg.OpCode; - Payload = msg.Payload; - } + public JToken Payload { get; set; } } } diff --git a/src/Discord.Net/Common/EventExtensions.cs b/src/Discord.Net/Common/EventExtensions.cs deleted file mode 100644 index a024822b7..000000000 --- a/src/Discord.Net/Common/EventExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Discord -{ - internal static class EventExtensions - { - public static void Raise(this EventHandler eventHandler, object sender) - => eventHandler?.Invoke(sender, EventArgs.Empty); - public static void Raise(this EventHandler eventHandler, object sender, T eventArgs) - where T : EventArgs - => eventHandler?.Invoke(sender, eventArgs); - } -} diff --git a/src/Discord.Net/ConnectionState.cs b/src/Discord.Net/ConnectionState.cs new file mode 100644 index 000000000..42c505ccd --- /dev/null +++ b/src/Discord.Net/ConnectionState.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + public enum ConnectionState : byte + { + Disconnected, + Connecting, + Connected, + Disconnecting + } +} diff --git a/src/Discord.Net/Common/DateTimeUtils.cs b/src/Discord.Net/DateTimeUtils.cs similarity index 79% rename from src/Discord.Net/Common/DateTimeUtils.cs rename to src/Discord.Net/DateTimeUtils.cs index 57db38134..92a42e74b 100644 --- a/src/Discord.Net/Common/DateTimeUtils.cs +++ b/src/Discord.Net/DateTimeUtils.cs @@ -5,6 +5,7 @@ namespace Discord internal static class DateTimeUtils { private const ulong EpochTicks = 621355968000000000UL; + private const ulong DiscordEpochMillis = 1420070400000UL; public static DateTime FromEpochMilliseconds(ulong value) => new DateTime((long)(value * TimeSpan.TicksPerMillisecond + EpochTicks), DateTimeKind.Utc); @@ -12,6 +13,6 @@ namespace Discord => new DateTime((long)(value * TimeSpan.TicksPerSecond + EpochTicks), DateTimeKind.Utc); public static DateTime FromSnowflake(ulong value) - => FromEpochMilliseconds((value >> 22) + 1420070400000UL); + => FromEpochMilliseconds((value >> 22) + DiscordEpochMillis); } } diff --git a/src/Discord.Net/Common/Entities/Channels/ChannelType.cs b/src/Discord.Net/Entities/Channels/ChannelType.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/ChannelType.cs rename to src/Discord.Net/Entities/Channels/ChannelType.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IChannel.cs b/src/Discord.Net/Entities/Channels/IChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/IChannel.cs rename to src/Discord.Net/Entities/Channels/IChannel.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IDMChannel.cs b/src/Discord.Net/Entities/Channels/IDMChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/IDMChannel.cs rename to src/Discord.Net/Entities/Channels/IDMChannel.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs b/src/Discord.Net/Entities/Channels/IGuildChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/IGuildChannel.cs rename to src/Discord.Net/Entities/Channels/IGuildChannel.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs b/src/Discord.Net/Entities/Channels/IMessageChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/IMessageChannel.cs rename to src/Discord.Net/Entities/Channels/IMessageChannel.cs diff --git a/src/Discord.Net/Common/Entities/Channels/ITextChannel.cs b/src/Discord.Net/Entities/Channels/ITextChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/ITextChannel.cs rename to src/Discord.Net/Entities/Channels/ITextChannel.cs diff --git a/src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net/Entities/Channels/IVoiceChannel.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Channels/IVoiceChannel.cs rename to src/Discord.Net/Entities/Channels/IVoiceChannel.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/Emoji.cs b/src/Discord.Net/Entities/Guilds/Emoji.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/Emoji.cs rename to src/Discord.Net/Entities/Guilds/Emoji.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuild.cs b/src/Discord.Net/Entities/Guilds/IGuild.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IGuild.cs rename to src/Discord.Net/Entities/Guilds/IGuild.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuildEmbed.cs b/src/Discord.Net/Entities/Guilds/IGuildEmbed.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IGuildEmbed.cs rename to src/Discord.Net/Entities/Guilds/IGuildEmbed.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs b/src/Discord.Net/Entities/Guilds/IGuildIntegration.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IGuildIntegration.cs rename to src/Discord.Net/Entities/Guilds/IGuildIntegration.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IUserGuild.cs b/src/Discord.Net/Entities/Guilds/IUserGuild.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IUserGuild.cs rename to src/Discord.Net/Entities/Guilds/IUserGuild.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net/Entities/Guilds/IVoiceRegion.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IVoiceRegion.cs rename to src/Discord.Net/Entities/Guilds/IVoiceRegion.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net/Entities/Guilds/IntegrationAccount.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/IntegrationAccount.cs rename to src/Discord.Net/Entities/Guilds/IntegrationAccount.cs diff --git a/src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs b/src/Discord.Net/Entities/Guilds/VoiceRegion.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Guilds/VoiceRegion.cs rename to src/Discord.Net/Entities/Guilds/VoiceRegion.cs diff --git a/src/Discord.Net/Common/Entities/IDeletable.cs b/src/Discord.Net/Entities/IDeletable.cs similarity index 100% rename from src/Discord.Net/Common/Entities/IDeletable.cs rename to src/Discord.Net/Entities/IDeletable.cs diff --git a/src/Discord.Net/Common/Entities/IEntity.cs b/src/Discord.Net/Entities/IEntity.cs similarity index 100% rename from src/Discord.Net/Common/Entities/IEntity.cs rename to src/Discord.Net/Entities/IEntity.cs diff --git a/src/Discord.Net/Common/Entities/IMentionable.cs b/src/Discord.Net/Entities/IMentionable.cs similarity index 100% rename from src/Discord.Net/Common/Entities/IMentionable.cs rename to src/Discord.Net/Entities/IMentionable.cs diff --git a/src/Discord.Net/Common/Entities/ISnowflakeEntity.cs b/src/Discord.Net/Entities/ISnowflakeEntity.cs similarity index 100% rename from src/Discord.Net/Common/Entities/ISnowflakeEntity.cs rename to src/Discord.Net/Entities/ISnowflakeEntity.cs diff --git a/src/Discord.Net/Common/Entities/IUpdateable.cs b/src/Discord.Net/Entities/IUpdateable.cs similarity index 100% rename from src/Discord.Net/Common/Entities/IUpdateable.cs rename to src/Discord.Net/Entities/IUpdateable.cs diff --git a/src/Discord.Net/Common/Entities/Invites/IInvite.cs b/src/Discord.Net/Entities/Invites/IInvite.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Invites/IInvite.cs rename to src/Discord.Net/Entities/Invites/IInvite.cs diff --git a/src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net/Entities/Invites/IInviteMetadata.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Invites/IInviteMetadata.cs rename to src/Discord.Net/Entities/Invites/IInviteMetadata.cs diff --git a/src/Discord.Net/Common/Entities/Invites/Invite.cs b/src/Discord.Net/Entities/Invites/Invite.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Invites/Invite.cs rename to src/Discord.Net/Entities/Invites/Invite.cs diff --git a/src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs b/src/Discord.Net/Entities/Invites/InviteMetadata.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Invites/InviteMetadata.cs rename to src/Discord.Net/Entities/Invites/InviteMetadata.cs diff --git a/src/Discord.Net/Common/Entities/Messages/Attachment.cs b/src/Discord.Net/Entities/Messages/Attachment.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/Attachment.cs rename to src/Discord.Net/Entities/Messages/Attachment.cs diff --git a/src/Discord.Net/Common/Entities/Messages/Direction.cs b/src/Discord.Net/Entities/Messages/Direction.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/Direction.cs rename to src/Discord.Net/Entities/Messages/Direction.cs diff --git a/src/Discord.Net/Common/Entities/Messages/Embed.cs b/src/Discord.Net/Entities/Messages/Embed.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/Embed.cs rename to src/Discord.Net/Entities/Messages/Embed.cs diff --git a/src/Discord.Net/Common/Entities/Messages/EmbedProvider.cs b/src/Discord.Net/Entities/Messages/EmbedProvider.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/EmbedProvider.cs rename to src/Discord.Net/Entities/Messages/EmbedProvider.cs diff --git a/src/Discord.Net/Common/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net/Entities/Messages/EmbedThumbnail.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/EmbedThumbnail.cs rename to src/Discord.Net/Entities/Messages/EmbedThumbnail.cs diff --git a/src/Discord.Net/Common/Entities/Messages/IMessage.cs b/src/Discord.Net/Entities/Messages/IMessage.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Messages/IMessage.cs rename to src/Discord.Net/Entities/Messages/IMessage.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net/Entities/Permissions/ChannelPermission.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/ChannelPermission.cs rename to src/Discord.Net/Entities/Permissions/ChannelPermission.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net/Entities/Permissions/ChannelPermissions.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/ChannelPermissions.cs rename to src/Discord.Net/Entities/Permissions/ChannelPermissions.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs b/src/Discord.Net/Entities/Permissions/GuildPermission.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/GuildPermission.cs rename to src/Discord.Net/Entities/Permissions/GuildPermission.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net/Entities/Permissions/GuildPermissions.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/GuildPermissions.cs rename to src/Discord.Net/Entities/Permissions/GuildPermissions.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/Overwrite.cs b/src/Discord.Net/Entities/Permissions/Overwrite.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/Overwrite.cs rename to src/Discord.Net/Entities/Permissions/Overwrite.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net/Entities/Permissions/OverwritePermissions.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/OverwritePermissions.cs rename to src/Discord.Net/Entities/Permissions/OverwritePermissions.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/PermValue.cs b/src/Discord.Net/Entities/Permissions/PermValue.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/PermValue.cs rename to src/Discord.Net/Entities/Permissions/PermValue.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/PermissionTarget.cs b/src/Discord.Net/Entities/Permissions/PermissionTarget.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/PermissionTarget.cs rename to src/Discord.Net/Entities/Permissions/PermissionTarget.cs diff --git a/src/Discord.Net/Common/Entities/Permissions/Permissions.cs b/src/Discord.Net/Entities/Permissions/Permissions.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Permissions/Permissions.cs rename to src/Discord.Net/Entities/Permissions/Permissions.cs diff --git a/src/Discord.Net/Common/Entities/Roles/Color.cs b/src/Discord.Net/Entities/Roles/Color.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Roles/Color.cs rename to src/Discord.Net/Entities/Roles/Color.cs diff --git a/src/Discord.Net/Common/Entities/Roles/IRole.cs b/src/Discord.Net/Entities/Roles/IRole.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Roles/IRole.cs rename to src/Discord.Net/Entities/Roles/IRole.cs diff --git a/src/Discord.Net/Common/Entities/Users/Connection.cs b/src/Discord.Net/Entities/Users/Connection.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/Connection.cs rename to src/Discord.Net/Entities/Users/Connection.cs diff --git a/src/Discord.Net/Common/Entities/Users/Game.cs b/src/Discord.Net/Entities/Users/Game.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/Game.cs rename to src/Discord.Net/Entities/Users/Game.cs diff --git a/src/Discord.Net/Common/Entities/Users/IConnection.cs b/src/Discord.Net/Entities/Users/IConnection.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/IConnection.cs rename to src/Discord.Net/Entities/Users/IConnection.cs diff --git a/src/Discord.Net/Common/Entities/Users/IGuildUser.cs b/src/Discord.Net/Entities/Users/IGuildUser.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/IGuildUser.cs rename to src/Discord.Net/Entities/Users/IGuildUser.cs diff --git a/src/Discord.Net/Common/Entities/Users/ISelfUser.cs b/src/Discord.Net/Entities/Users/ISelfUser.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/ISelfUser.cs rename to src/Discord.Net/Entities/Users/ISelfUser.cs diff --git a/src/Discord.Net/Common/Entities/Users/IUser.cs b/src/Discord.Net/Entities/Users/IUser.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/IUser.cs rename to src/Discord.Net/Entities/Users/IUser.cs diff --git a/src/Discord.Net/Common/Entities/Users/IVoiceState.cs.old b/src/Discord.Net/Entities/Users/IVoiceState.cs.old similarity index 100% rename from src/Discord.Net/Common/Entities/Users/IVoiceState.cs.old rename to src/Discord.Net/Entities/Users/IVoiceState.cs.old diff --git a/src/Discord.Net/Common/Entities/Users/StreamType.cs b/src/Discord.Net/Entities/Users/StreamType.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/StreamType.cs rename to src/Discord.Net/Entities/Users/StreamType.cs diff --git a/src/Discord.Net/Common/Entities/Users/UserStatus.cs b/src/Discord.Net/Entities/Users/UserStatus.cs similarity index 100% rename from src/Discord.Net/Common/Entities/Users/UserStatus.cs rename to src/Discord.Net/Entities/Users/UserStatus.cs diff --git a/src/Discord.Net/EventExtensions.cs b/src/Discord.Net/EventExtensions.cs new file mode 100644 index 000000000..f961866d1 --- /dev/null +++ b/src/Discord.Net/EventExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + internal static class EventExtensions + { + public static async Task Raise(this Func eventHandler) + { + var subscriptions = eventHandler?.GetInvocationList(); + if (subscriptions != null) + { + for (int i = 0; i < subscriptions.Length; i++) + await (subscriptions[i] as Func).Invoke().ConfigureAwait(false); + } + } + public static async Task Raise(this Func eventHandler, T arg) + { + var subscriptions = eventHandler?.GetInvocationList(); + if (subscriptions != null) + { + for (int i = 0; i < subscriptions.Length; i++) + await (subscriptions[i] as Func).Invoke(arg).ConfigureAwait(false); + } + } + public static async Task Raise(this Func eventHandler, T1 arg1, T2 arg2) + { + var subscriptions = eventHandler?.GetInvocationList(); + if (subscriptions != null) + { + for (int i = 0; i < subscriptions.Length; i++) + await (subscriptions[i] as Func).Invoke(arg1, arg2).ConfigureAwait(false); + } + } + public static async Task Raise(this Func eventHandler, T1 arg1, T2 arg2, T3 arg3) + { + var subscriptions = eventHandler?.GetInvocationList(); + if (subscriptions != null) + { + for (int i = 0; i < subscriptions.Length; i++) + await (subscriptions[i] as Func).Invoke(arg1, arg2, arg3).ConfigureAwait(false); + } + } + } +} diff --git a/src/Discord.Net/Common/Events/LogMessageEventArgs.cs b/src/Discord.Net/Events/LogMessageEventArgs.cs similarity index 100% rename from src/Discord.Net/Common/Events/LogMessageEventArgs.cs rename to src/Discord.Net/Events/LogMessageEventArgs.cs diff --git a/src/Discord.Net/Common/Events/SentRequestEventArgs.cs b/src/Discord.Net/Events/SentRequestEventArgs.cs similarity index 100% rename from src/Discord.Net/Common/Events/SentRequestEventArgs.cs rename to src/Discord.Net/Events/SentRequestEventArgs.cs diff --git a/src/Discord.Net/IDiscordClient.cs b/src/Discord.Net/IDiscordClient.cs index 3bdf82a43..21c3c477c 100644 --- a/src/Discord.Net/IDiscordClient.cs +++ b/src/Discord.Net/IDiscordClient.cs @@ -1,5 +1,6 @@ using Discord.API; -using Discord.Net.Rest; +using Discord.Net.Queue; +using Discord.WebSocket.Data; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; @@ -9,15 +10,20 @@ namespace Discord //TODO: Add docstrings public interface IDiscordClient { - TokenType AuthTokenType { get; } + LoginState LoginState { get; } + ConnectionState ConnectionState { get; } + DiscordApiClient ApiClient { get; } - IRestClient RestClient { get; } IRequestQueue RequestQueue { get; } + IDataStore DataStore { get; } Task Login(string email, string password); Task Login(TokenType tokenType, string token, bool validateToken = true); Task Logout(); + Task Connect(); + Task Disconnect(); + Task GetChannel(ulong id); Task> GetDMChannels(); diff --git a/src/Discord.Net/Logging/ILogger.cs b/src/Discord.Net/Logging/ILogger.cs index f8679d0ec..ccc7f06f7 100644 --- a/src/Discord.Net/Logging/ILogger.cs +++ b/src/Discord.Net/Logging/ILogger.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Discord.Logging { @@ -6,28 +7,28 @@ namespace Discord.Logging { LogSeverity Level { get; } - void Log(LogSeverity severity, string message, Exception exception = null); - void Log(LogSeverity severity, FormattableString message, Exception exception = null); - void Log(LogSeverity severity, Exception exception); + Task Log(LogSeverity severity, string message, Exception exception = null); + Task Log(LogSeverity severity, FormattableString message, Exception exception = null); + Task Log(LogSeverity severity, Exception exception); - void Error(string message, Exception exception = null); - void Error(FormattableString message, Exception exception = null); - void Error(Exception exception); + Task Error(string message, Exception exception = null); + Task Error(FormattableString message, Exception exception = null); + Task Error(Exception exception); - void Warning(string message, Exception exception = null); - void Warning(FormattableString message, Exception exception = null); - void Warning(Exception exception); + Task Warning(string message, Exception exception = null); + Task Warning(FormattableString message, Exception exception = null); + Task Warning(Exception exception); - void Info(string message, Exception exception = null); - void Info(FormattableString message, Exception exception = null); - void Info(Exception exception); + Task Info(string message, Exception exception = null); + Task Info(FormattableString message, Exception exception = null); + Task Info(Exception exception); - void Verbose(string message, Exception exception = null); - void Verbose(FormattableString message, Exception exception = null); - void Verbose(Exception exception); + Task Verbose(string message, Exception exception = null); + Task Verbose(FormattableString message, Exception exception = null); + Task Verbose(Exception exception); - void Debug(string message, Exception exception = null); - void Debug(FormattableString message, Exception exception = null); - void Debug(Exception exception); + Task Debug(string message, Exception exception = null); + Task Debug(FormattableString message, Exception exception = null); + Task Debug(Exception exception); } } diff --git a/src/Discord.Net/Logging/LogManager.cs b/src/Discord.Net/Logging/LogManager.cs index 0c183071d..bcbfbd20a 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net/Logging/LogManager.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Discord.Logging { @@ -6,107 +7,107 @@ namespace Discord.Logging { public LogSeverity Level { get; } - public event EventHandler Message = delegate { }; + public event Func Message; internal LogManager(LogSeverity minSeverity) { Level = minSeverity; } - public void Log(LogSeverity severity, string source, string message, Exception ex = null) + public async Task Log(LogSeverity severity, string source, string message, Exception ex = null) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, source, message, ex)); + await Message.Raise(new LogMessageEventArgs(severity, source, message, ex)).ConfigureAwait(false); } - public void Log(LogSeverity severity, string source, FormattableString message, Exception ex = null) + public async Task Log(LogSeverity severity, string source, FormattableString message, Exception ex = null) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, source, message.ToString(), ex)); + await Message.Raise(new LogMessageEventArgs(severity, source, message.ToString(), ex)).ConfigureAwait(false); } - public void Log(LogSeverity severity, string source, Exception ex) + public async Task Log(LogSeverity severity, string source, Exception ex) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, source, null, ex)); + await Message.Raise(new LogMessageEventArgs(severity, source, null, ex)).ConfigureAwait(false); } - void ILogger.Log(LogSeverity severity, string message, Exception ex) + async Task ILogger.Log(LogSeverity severity, string message, Exception ex) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, "Discord", message, ex)); + await Message.Raise(new LogMessageEventArgs(severity, "Discord", message, ex)).ConfigureAwait(false); } - void ILogger.Log(LogSeverity severity, FormattableString message, Exception ex) + async Task ILogger.Log(LogSeverity severity, FormattableString message, Exception ex) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, "Discord", message.ToString(), ex)); + await Message.Raise(new LogMessageEventArgs(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); } - void ILogger.Log(LogSeverity severity, Exception ex) + async Task ILogger.Log(LogSeverity severity, Exception ex) { if (severity <= Level) - Message(this, new LogMessageEventArgs(severity, "Discord", null, ex)); + await Message.Raise(new LogMessageEventArgs(severity, "Discord", null, ex)).ConfigureAwait(false); } - public void Error(string source, string message, Exception ex = null) + public Task Error(string source, string message, Exception ex = null) => Log(LogSeverity.Error, source, message, ex); - public void Error(string source, FormattableString message, Exception ex = null) + public Task Error(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Error, source, message, ex); - public void Error(string source, Exception ex) + public Task Error(string source, Exception ex) => Log(LogSeverity.Error, source, ex); - void ILogger.Error(string message, Exception ex) + Task ILogger.Error(string message, Exception ex) => Log(LogSeverity.Error, "Discord", message, ex); - void ILogger.Error(FormattableString message, Exception ex) + Task ILogger.Error(FormattableString message, Exception ex) => Log(LogSeverity.Error, "Discord", message, ex); - void ILogger.Error(Exception ex) + Task ILogger.Error(Exception ex) => Log(LogSeverity.Error, "Discord", ex); - public void Warning(string source, string message, Exception ex = null) + public Task Warning(string source, string message, Exception ex = null) => Log(LogSeverity.Warning, source, message, ex); - public void Warning(string source, FormattableString message, Exception ex = null) + public Task Warning(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Warning, source, message, ex); - public void Warning(string source, Exception ex) + public Task Warning(string source, Exception ex) => Log(LogSeverity.Warning, source, ex); - void ILogger.Warning(string message, Exception ex) + Task ILogger.Warning(string message, Exception ex) => Log(LogSeverity.Warning, "Discord", message, ex); - void ILogger.Warning(FormattableString message, Exception ex) + Task ILogger.Warning(FormattableString message, Exception ex) => Log(LogSeverity.Warning, "Discord", message, ex); - void ILogger.Warning(Exception ex) + Task ILogger.Warning(Exception ex) => Log(LogSeverity.Warning, "Discord", ex); - public void Info(string source, string message, Exception ex = null) + public Task Info(string source, string message, Exception ex = null) => Log(LogSeverity.Info, source, message, ex); - public void Info(string source, FormattableString message, Exception ex = null) + public Task Info(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Info, source, message, ex); - public void Info(string source, Exception ex) + public Task Info(string source, Exception ex) => Log(LogSeverity.Info, source, ex); - void ILogger.Info(string message, Exception ex) + Task ILogger.Info(string message, Exception ex) => Log(LogSeverity.Info, "Discord", message, ex); - void ILogger.Info(FormattableString message, Exception ex) + Task ILogger.Info(FormattableString message, Exception ex) => Log(LogSeverity.Info, "Discord", message, ex); - void ILogger.Info(Exception ex) + Task ILogger.Info(Exception ex) => Log(LogSeverity.Info, "Discord", ex); - public void Verbose(string source, string message, Exception ex = null) + public Task Verbose(string source, string message, Exception ex = null) => Log(LogSeverity.Verbose, source, message, ex); - public void Verbose(string source, FormattableString message, Exception ex = null) + public Task Verbose(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Verbose, source, message, ex); - public void Verbose(string source, Exception ex) + public Task Verbose(string source, Exception ex) => Log(LogSeverity.Verbose, source, ex); - void ILogger.Verbose(string message, Exception ex) + Task ILogger.Verbose(string message, Exception ex) => Log(LogSeverity.Verbose, "Discord", message, ex); - void ILogger.Verbose(FormattableString message, Exception ex) + Task ILogger.Verbose(FormattableString message, Exception ex) => Log(LogSeverity.Verbose, "Discord", message, ex); - void ILogger.Verbose(Exception ex) + Task ILogger.Verbose(Exception ex) => Log(LogSeverity.Verbose, "Discord", ex); - public void Debug(string source, string message, Exception ex = null) + public Task Debug(string source, string message, Exception ex = null) => Log(LogSeverity.Debug, source, message, ex); - public void Debug(string source, FormattableString message, Exception ex = null) + public Task Debug(string source, FormattableString message, Exception ex = null) => Log(LogSeverity.Debug, source, message, ex); - public void Debug(string source, Exception ex) + public Task Debug(string source, Exception ex) => Log(LogSeverity.Debug, source, ex); - void ILogger.Debug(string message, Exception ex) + Task ILogger.Debug(string message, Exception ex) => Log(LogSeverity.Debug, "Discord", message, ex); - void ILogger.Debug(FormattableString message, Exception ex) + Task ILogger.Debug(FormattableString message, Exception ex) => Log(LogSeverity.Debug, "Discord", message, ex); - void ILogger.Debug(Exception ex) + Task ILogger.Debug(Exception ex) => Log(LogSeverity.Debug, "Discord", ex); internal Logger CreateLogger(string name) => new Logger(this, name); diff --git a/src/Discord.Net/LoginState.cs b/src/Discord.Net/LoginState.cs new file mode 100644 index 000000000..42b6ecac9 --- /dev/null +++ b/src/Discord.Net/LoginState.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + public enum LoginState : byte + { + LoggedOut, + LoggingIn, + LoggedIn, + LoggingOut + } +} diff --git a/src/Discord.Net/Common/MentionUtils.cs b/src/Discord.Net/MentionUtils.cs similarity index 100% rename from src/Discord.Net/Common/MentionUtils.cs rename to src/Discord.Net/MentionUtils.cs diff --git a/src/Discord.Net/Net/Rest/RequestQueue/BucketGroup.cs b/src/Discord.Net/Net/Queue/BucketGroup.cs similarity index 71% rename from src/Discord.Net/Net/Rest/RequestQueue/BucketGroup.cs rename to src/Discord.Net/Net/Queue/BucketGroup.cs index 54c3e717d..161f08432 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/BucketGroup.cs +++ b/src/Discord.Net/Net/Queue/BucketGroup.cs @@ -1,4 +1,4 @@ -namespace Discord.Net.Rest +namespace Discord.Net.Queue { internal enum BucketGroup { diff --git a/src/Discord.Net/Net/Queue/GlobalBucket.cs b/src/Discord.Net/Net/Queue/GlobalBucket.cs new file mode 100644 index 000000000..d1e011ffd --- /dev/null +++ b/src/Discord.Net/Net/Queue/GlobalBucket.cs @@ -0,0 +1,12 @@ +namespace Discord.Net.Queue +{ + public enum GlobalBucket + { + General, + Login, + DirectMessage, + SendEditMessage, + Gateway, + UpdateStatus + } +} diff --git a/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs b/src/Discord.Net/Net/Queue/GuildBucket.cs similarity index 83% rename from src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs rename to src/Discord.Net/Net/Queue/GuildBucket.cs index cb4c0d9a8..4089fd1e7 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/GuildBucket.cs +++ b/src/Discord.Net/Net/Queue/GuildBucket.cs @@ -1,4 +1,4 @@ -namespace Discord.Net.Rest +namespace Discord.Net.Queue { public enum GuildBucket { diff --git a/src/Discord.Net/Net/Queue/IQueuedRequest.cs b/src/Discord.Net/Net/Queue/IQueuedRequest.cs new file mode 100644 index 000000000..e5575046e --- /dev/null +++ b/src/Discord.Net/Net/Queue/IQueuedRequest.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Queue +{ + internal interface IQueuedRequest + { + TaskCompletionSource Promise { get; } + CancellationToken CancelToken { get; } + Task Send(); + } +} diff --git a/src/Discord.Net/Net/Rest/RequestQueue/IRequestQueue.cs b/src/Discord.Net/Net/Queue/IRequestQueue.cs similarity index 87% rename from src/Discord.Net/Net/Rest/RequestQueue/IRequestQueue.cs rename to src/Discord.Net/Net/Queue/IRequestQueue.cs index 67adbf924..75a820934 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/IRequestQueue.cs +++ b/src/Discord.Net/Net/Queue/IRequestQueue.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Discord.Net.Rest +namespace Discord.Net.Queue { //TODO: Add docstrings public interface IRequestQueue diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs b/src/Discord.Net/Net/Queue/RequestQueue.cs similarity index 92% rename from src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs rename to src/Discord.Net/Net/Queue/RequestQueue.cs index 9bf2189fe..91ad91711 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueue.cs +++ b/src/Discord.Net/Net/Queue/RequestQueue.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.Rest +namespace Discord.Net.Queue { public class RequestQueue : IRequestQueue { @@ -15,12 +15,8 @@ namespace Discord.Net.Rest private CancellationToken? _parentToken; private CancellationToken _cancelToken; - public IRestClient RestClient { get; } - - public RequestQueue(IRestClient restClient) + public RequestQueue() { - RestClient = restClient; - _lock = new SemaphoreSlim(1, 1); _globalBuckets = new RequestQueueBucket[Enum.GetValues(typeof(GlobalBucket)).Length]; _guildBuckets = new Dictionary[Enum.GetValues(typeof(GuildBucket)).Length]; @@ -38,12 +34,10 @@ namespace Discord.Net.Rest finally { Unlock(); } } - internal async Task Send(RestRequest request, BucketGroup group, int bucketId, ulong guildId) + internal async Task Send(IQueuedRequest request, BucketGroup group, int bucketId, ulong guildId) { RequestQueueBucket bucket; - request.CancelToken = _cancelToken; - await Lock().ConfigureAwait(false); try { @@ -66,6 +60,9 @@ namespace Discord.Net.Rest case GlobalBucket.General: return new RequestQueueBucket(this, bucket, int.MaxValue, 0); //Catch-all case GlobalBucket.Login: return new RequestQueueBucket(this, bucket, 1, 1); //TODO: Is this actual logins or token validations too? case GlobalBucket.DirectMessage: return new RequestQueueBucket(this, bucket, 5, 5); + case GlobalBucket.SendEditMessage: return new RequestQueueBucket(this, bucket, 50, 10); + case GlobalBucket.Gateway: return new RequestQueueBucket(this, bucket, 120, 60); + case GlobalBucket.UpdateStatus: return new RequestQueueBucket(this, bucket, 5, 1, GlobalBucket.Gateway); default: throw new ArgumentException($"Unknown global bucket: {bucket}", nameof(bucket)); } @@ -75,7 +72,7 @@ namespace Discord.Net.Rest switch (bucket) { //Per Guild - case GuildBucket.SendEditMessage: return new RequestQueueBucket(this, bucket, guildId, 5, 5); + case GuildBucket.SendEditMessage: return new RequestQueueBucket(this, bucket, guildId, 5, 5, GlobalBucket.SendEditMessage); case GuildBucket.DeleteMessage: return new RequestQueueBucket(this, bucket, guildId, 5, 1); case GuildBucket.DeleteMessages: return new RequestQueueBucket(this, bucket, guildId, 1, 1); case GuildBucket.ModifyMember: return new RequestQueueBucket(this, bucket, guildId, 10, 10); //TODO: Is this all users or just roles? diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs b/src/Discord.Net/Net/Queue/RequestQueueBucket.cs similarity index 86% rename from src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs rename to src/Discord.Net/Net/Queue/RequestQueueBucket.cs index 708e3251c..7b05fb0fe 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs +++ b/src/Discord.Net/Net/Queue/RequestQueueBucket.cs @@ -5,15 +5,17 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.Rest +namespace Discord.Net.Queue { + //TODO: Implement bucket chaining internal class RequestQueueBucket { private readonly RequestQueue _parent; private readonly BucketGroup _bucketGroup; + private readonly GlobalBucket? _chainedBucket; private readonly int _bucketId; private readonly ulong _guildId; - private readonly ConcurrentQueue _queue; + private readonly ConcurrentQueue _queue; private readonly SemaphoreSlim _lock; private Task _resetTask; private bool _waitingToProcess; @@ -23,31 +25,32 @@ namespace Discord.Net.Rest public int WindowSeconds { get; } public int WindowCount { get; private set; } - public RequestQueueBucket(RequestQueue parent, GlobalBucket bucket, int windowMaxCount, int windowSeconds) - : this(parent, windowMaxCount, windowSeconds) + public RequestQueueBucket(RequestQueue parent, GlobalBucket bucket, int windowMaxCount, int windowSeconds, GlobalBucket? chainedBucket = null) + : this(parent, windowMaxCount, windowSeconds, chainedBucket) { _bucketGroup = BucketGroup.Global; _bucketId = (int)bucket; _guildId = 0; } - public RequestQueueBucket(RequestQueue parent, GuildBucket bucket, ulong guildId, int windowMaxCount, int windowSeconds) - : this(parent, windowMaxCount, windowSeconds) + public RequestQueueBucket(RequestQueue parent, GuildBucket bucket, ulong guildId, int windowMaxCount, int windowSeconds, GlobalBucket? chainedBucket = null) + : this(parent, windowMaxCount, windowSeconds, chainedBucket) { _bucketGroup = BucketGroup.Guild; _bucketId = (int)bucket; _guildId = guildId; } - private RequestQueueBucket(RequestQueue parent, int windowMaxCount, int windowSeconds) + private RequestQueueBucket(RequestQueue parent, int windowMaxCount, int windowSeconds, GlobalBucket? chainedBucket = null) { _parent = parent; WindowMaxCount = windowMaxCount; WindowSeconds = windowSeconds; - _queue = new ConcurrentQueue(); + _chainedBucket = chainedBucket; + _queue = new ConcurrentQueue(); _lock = new SemaphoreSlim(1, 1); _id = new System.Random().Next(0, int.MaxValue); } - public void Queue(RestRequest request) + public void Queue(IQueuedRequest request) { _queue.Enqueue(request); } @@ -68,7 +71,7 @@ namespace Discord.Net.Rest _waitingToProcess = false; while (true) { - RestRequest request; + IQueuedRequest request; //If we're waiting to reset (due to a rate limit exception, or preemptive check), abort if (WindowCount == WindowMaxCount) return; @@ -81,11 +84,7 @@ namespace Discord.Net.Rest request.Promise.SetException(new OperationCanceledException(request.CancelToken)); else { - Stream stream; - if (request.IsMultipart) - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.CancelToken, request.MultipartParams, request.HeaderOnly).ConfigureAwait(false); - else - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.CancelToken, request.Json, request.HeaderOnly).ConfigureAwait(false); + Stream stream = await request.Send().ConfigureAwait(false); request.Promise.SetResult(stream); } } @@ -157,7 +156,7 @@ namespace Discord.Net.Rest public void Clear() { //Assume this obj is under lock - RestRequest request; + IQueuedRequest request; while (_queue.TryDequeue(out request)) { } } diff --git a/src/Discord.Net/Net/Queue/RestRequest.cs b/src/Discord.Net/Net/Queue/RestRequest.cs new file mode 100644 index 000000000..49c557dfd --- /dev/null +++ b/src/Discord.Net/Net/Queue/RestRequest.cs @@ -0,0 +1,53 @@ +using Discord.Net.Rest; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Queue +{ + internal class RestRequest : IQueuedRequest + { + public IRestClient Client { get; } + public string Method { get; } + public string Endpoint { get; } + public string Json { get; } + public bool HeaderOnly { get; } + public IReadOnlyDictionary MultipartParams { get; } + public TaskCompletionSource Promise { get; } + public CancellationToken CancelToken { get; internal set; } + + public bool IsMultipart => MultipartParams != null; + + public RestRequest(IRestClient client, string method, string endpoint, string json, bool headerOnly) + : this(client, method, endpoint, headerOnly) + { + Json = json; + } + + public RestRequest(IRestClient client, string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly) + : this(client, method, endpoint, headerOnly) + { + MultipartParams = multipartParams; + } + + private RestRequest(IRestClient client, string method, string endpoint, bool headerOnly) + { + Client = client; + Method = method; + Endpoint = endpoint; + Json = null; + MultipartParams = null; + HeaderOnly = headerOnly; + Promise = new TaskCompletionSource(); + } + + public async Task Send() + { + if (IsMultipart) + return await Client.Send(Method, Endpoint, MultipartParams, HeaderOnly).ConfigureAwait(false); + else + return await Client.Send(Method, Endpoint, Json, HeaderOnly).ConfigureAwait(false); + } + } +} diff --git a/src/Discord.Net/Net/Queue/WebSocketRequest.cs b/src/Discord.Net/Net/Queue/WebSocketRequest.cs new file mode 100644 index 000000000..47a00ad68 --- /dev/null +++ b/src/Discord.Net/Net/Queue/WebSocketRequest.cs @@ -0,0 +1,34 @@ +using Discord.Net.WebSockets; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Queue +{ + internal class WebSocketRequest : IQueuedRequest + { + public IWebSocketClient Client { get; } + public byte[] Data { get; } + public int Offset { get; } + public int Bytes { get; } + public bool IsText { get; } + public CancellationToken CancelToken { get; } + public TaskCompletionSource Promise { get; } + + public WebSocketRequest(byte[] data, bool isText, CancellationToken cancelToken) : this(data, 0, data.Length, isText, cancelToken) { } + public WebSocketRequest(byte[] data, int offset, int length, bool isText, CancellationToken cancelToken) + { + Data = data; + Offset = offset; + Bytes = length; + IsText = isText; + Promise = new TaskCompletionSource(); + } + + public async Task Send() + { + await Client.Send(Data, Offset, Bytes, IsText).ConfigureAwait(false); + return null; + } + } +} diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net/Net/Rest/DefaultRestClient.cs index 07a29342b..8166f23a7 100644 --- a/src/Discord.Net/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net/Net/Rest/DefaultRestClient.cs @@ -17,6 +17,8 @@ namespace Discord.Net.Rest protected readonly HttpClient _client; protected readonly string _baseUrl; + private CancellationTokenSource _cancelTokenSource; + private CancellationToken _cancelToken, _parentToken; protected bool _isDisposed; public DefaultRestClient(string baseUrl) @@ -32,6 +34,7 @@ namespace Discord.Net.Rest }); SetHeader("accept-encoding", "gzip, deflate"); + _parentToken = CancellationToken.None; } protected virtual void Dispose(bool disposing) { @@ -53,19 +56,28 @@ namespace Discord.Net.Rest if (value != null) _client.DefaultRequestHeaders.Add(key, value); } + public void SetCancelToken(CancellationToken cancelToken) + { + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + } - public async Task Send(string method, string endpoint, CancellationToken cancelToken, string json = null, bool headerOnly = false) + public async Task Send(string method, string endpoint, bool headerOnly = false) + { + string uri = Path.Combine(_baseUrl, endpoint); + using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) + return await SendInternal(restRequest, headerOnly).ConfigureAwait(false); + } + public async Task Send(string method, string endpoint, string json, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { - if (json != null) - restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return await SendInternal(restRequest, cancelToken, headerOnly).ConfigureAwait(false); + restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); + return await SendInternal(restRequest, headerOnly).ConfigureAwait(false); } } - - public async Task Send(string method, string endpoint, CancellationToken cancelToken, IReadOnlyDictionary multipartParams, bool headerOnly = false) + public async Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -112,14 +124,15 @@ namespace Discord.Net.Rest } } restRequest.Content = content; - return await SendInternal(restRequest, cancelToken, headerOnly).ConfigureAwait(false); + return await SendInternal(restRequest, headerOnly).ConfigureAwait(false); } } - private async Task SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) + private async Task SendInternal(HttpRequestMessage request, bool headerOnly) { while (true) { + var cancelToken = _cancelToken; //It's okay if another thread changes this, causes a retry to abort HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); int statusCode = (int)response.StatusCode; diff --git a/src/Discord.Net/Net/Rest/IRestClient.cs b/src/Discord.Net/Net/Rest/IRestClient.cs index 93740fc95..25b577688 100644 --- a/src/Discord.Net/Net/Rest/IRestClient.cs +++ b/src/Discord.Net/Net/Rest/IRestClient.cs @@ -9,8 +9,10 @@ namespace Discord.Net.Rest public interface IRestClient { void SetHeader(string key, string value); + void SetCancelToken(CancellationToken cancelToken); - Task Send(string method, string endpoint, CancellationToken cancelToken, string json = null, bool headerOnly = false); - Task Send(string method, string endpoint, CancellationToken cancelToken, IReadOnlyDictionary multipartParams, bool headerOnly = false); + Task Send(string method, string endpoint, bool headerOnly = false); + Task Send(string method, string endpoint, string json, bool headerOnly = false); + Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false); } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs b/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs deleted file mode 100644 index 9ce523155..000000000 --- a/src/Discord.Net/Net/Rest/RequestQueue/GlobalBucket.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Discord.Net.Rest -{ - public enum GlobalBucket - { - General, - Login, - DirectMessage - } -} diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs b/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs deleted file mode 100644 index 715333873..000000000 --- a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord.Net.Rest -{ - internal class RestRequest - { - public string Method { get; } - public string Endpoint { get; } - public string Json { get; } - public bool HeaderOnly { get; } - public CancellationToken CancelToken { get; internal set; } - public IReadOnlyDictionary MultipartParams { get; } - public TaskCompletionSource Promise { get; } - - public bool IsMultipart => MultipartParams != null; - - public RestRequest(string method, string endpoint, string json, bool headerOnly) - : this(method, endpoint, headerOnly) - { - Json = json; - } - - public RestRequest(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly) - : this(method, endpoint, headerOnly) - { - MultipartParams = multipartParams; - } - - private RestRequest(string method, string endpoint, bool headerOnly) - { - Method = method; - Endpoint = endpoint; - Json = null; - MultipartParams = null; - HeaderOnly = headerOnly; - Promise = new TaskCompletionSource(); - } - } -} diff --git a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs index 23b2fd015..8eee70c0c 100644 --- a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs +++ b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.ComponentModel; using System.IO; using System.Net.WebSockets; @@ -13,26 +12,25 @@ namespace Discord.Net.WebSockets { public const int ReceiveChunkSize = 12 * 1024; //12KB public const int SendChunkSize = 4 * 1024; //4KB - protected const int HR_TIMEOUT = -2147012894; + private const int HR_TIMEOUT = -2147012894; - public event EventHandler BinaryMessage = delegate { }; - public event EventHandler TextMessage = delegate { }; - - protected readonly ConcurrentQueue _sendQueue; - protected readonly ClientWebSocket _client; - protected Task _receiveTask, _sendTask; - protected CancellationTokenSource _cancelToken; - protected bool _isDisposed; + public event Func BinaryMessage; + public event Func TextMessage; + + private readonly ClientWebSocket _client; + private Task _task; + private CancellationTokenSource _cancelTokenSource; + private CancellationToken _cancelToken, _parentToken; + private bool _isDisposed; public DefaultWebSocketClient() { - _sendQueue = new ConcurrentQueue(); - _client = new ClientWebSocket(); _client.Options.Proxy = null; _client.Options.KeepAliveInterval = TimeSpan.Zero; + _parentToken = CancellationToken.None; } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!_isDisposed) { @@ -46,135 +44,106 @@ namespace Discord.Net.WebSockets Dispose(true); } - public async Task Connect(string host, CancellationToken cancelToken) + public async Task Connect(string host) { await Disconnect().ConfigureAwait(false); - _cancelToken = new CancellationTokenSource(); - var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken.Token, cancelToken).Token; + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; - await _client.ConnectAsync(new Uri(host), combinedToken).ConfigureAwait(false); - _receiveTask = ReceiveAsync(combinedToken); - _sendTask = SendAsync(combinedToken); + await _client.ConnectAsync(new Uri(host), _cancelToken).ConfigureAwait(false); + _task = Run(_cancelToken); } public async Task Disconnect() { - _cancelToken.Cancel(); - - string ignored; - while (_sendQueue.TryDequeue(out ignored)) { } + _cancelTokenSource.Cancel(); _client.Abort(); - - var receiveTask = _receiveTask ?? Task.CompletedTask; - var sendTask = _sendTask ?? Task.CompletedTask; - await Task.WhenAll(receiveTask, sendTask).ConfigureAwait(false); + + await (_task ?? Task.CompletedTask).ConfigureAwait(false); } public void SetHeader(string key, string value) { _client.Options.SetRequestHeader(key, value); } - - public void QueueMessage(string message) + public void SetCancelToken(CancellationToken cancelToken) { - _sendQueue.Enqueue(message); + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - //TODO: Check this code - private Task ReceiveAsync(CancellationToken cancelToken) + public async Task Send(byte[] data, int offset, int count, bool isText) { - return Task.Run(async () => + int frameCount = (int)Math.Ceiling((double)count / SendChunkSize); + + for (int i = 0; i < frameCount; i++, offset += SendChunkSize) { - var buffer = new ArraySegment(new byte[ReceiveChunkSize]); - var stream = new MemoryStream(); + bool isLast = i == (frameCount - 1); + + int frameSize; + if (isLast) + frameSize = count - (i * SendChunkSize); + else + frameSize = SendChunkSize; try { - while (!cancelToken.IsCancellationRequested) - { - WebSocketReceiveResult result = null; - do - { - if (cancelToken.IsCancellationRequested) return; - - try - { - result = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); - } - catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) - { - throw new Exception($"Connection timed out."); - } - - if (result.MessageType == WebSocketMessageType.Close) - throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); - else - stream.Write(buffer.Array, 0, result.Count); - - } - while (result == null || !result.EndOfMessage); - - var array = stream.ToArray(); - if (result.MessageType == WebSocketMessageType.Binary) - BinaryMessage(this, new BinaryMessageEventArgs(array)); - else if (result.MessageType == WebSocketMessageType.Text) - { - string text = Encoding.UTF8.GetString(array, 0, array.Length); - TextMessage(this, new TextMessageEventArgs(text)); - } - - stream.Position = 0; - stream.SetLength(0); - } + await _client.SendAsync(new ArraySegment(data, offset, count), isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary, isLast, _cancelToken).ConfigureAwait(false); } - catch (OperationCanceledException) { } - }); + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + return; + } + } } //TODO: Check this code - private Task SendAsync(CancellationToken cancelToken) + private async Task Run(CancellationToken cancelToken) { - return Task.Run(async () => - { - byte[] bytes = new byte[SendChunkSize]; + var buffer = new ArraySegment(new byte[ReceiveChunkSize]); + var stream = new MemoryStream(); - try + try + { + while (!cancelToken.IsCancellationRequested) { - while (!cancelToken.IsCancellationRequested) + WebSocketReceiveResult result = null; + do { - string json; - while (_sendQueue.TryDequeue(out json)) + if (cancelToken.IsCancellationRequested) return; + + try + { + result = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) { - int byteCount = Encoding.UTF8.GetBytes(json, 0, json.Length, bytes, 0); - int frameCount = (int)Math.Ceiling((double)byteCount / SendChunkSize); - - int offset = 0; - for (int i = 0; i < frameCount; i++, offset += SendChunkSize) - { - bool isLast = i == (frameCount - 1); - - int count; - if (isLast) - count = byteCount - (i * SendChunkSize); - else - count = SendChunkSize; - - try - { - await _client.SendAsync(new ArraySegment(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); - } - catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) - { - return; - } - } + throw new Exception("Connection timed out."); } - await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); + + if (result.MessageType == WebSocketMessageType.Close) + throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); + else + stream.Write(buffer.Array, 0, result.Count); + + } + while (result == null || !result.EndOfMessage); + + var array = stream.ToArray(); + if (result.MessageType == WebSocketMessageType.Binary) + await BinaryMessage.Raise(new BinaryMessageEventArgs(array)).ConfigureAwait(false); + else if (result.MessageType == WebSocketMessageType.Text) + { + string text = Encoding.UTF8.GetString(array, 0, array.Length); + await TextMessage.Raise(new TextMessageEventArgs(text)).ConfigureAwait(false); } + + stream.Position = 0; + stream.SetLength(0); } - catch (OperationCanceledException) { } - }); + } + catch (OperationCanceledException) { } } } } diff --git a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs index e6bbeb402..20cfd0da8 100644 --- a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs +++ b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs @@ -7,13 +7,15 @@ namespace Discord.Net.WebSockets //TODO: Add ETF public interface IWebSocketClient { - event EventHandler BinaryMessage; - event EventHandler TextMessage; + event Func BinaryMessage; + event Func TextMessage; void SetHeader(string key, string value); + void SetCancelToken(CancellationToken cancelToken); - Task Connect(string host, CancellationToken cancelToken); + Task Connect(string host); Task Disconnect(); - void QueueMessage(string message); + + Task Send(byte[] data, int offset, int length, bool isText); } } diff --git a/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs b/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs index ab41404b9..88f467221 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocketProvider.cs @@ -1,4 +1,4 @@ namespace Discord.Net.WebSockets { - public delegate IWebSocketClient WebSocketProvider(string baseUrl); + public delegate IWebSocketClient WebSocketProvider(); } diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index 90ab61ea1..992b52c92 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -1,5 +1,7 @@ using Discord.API.Rest; using Discord.Logging; +using Discord.Net; +using Discord.Net.Queue; using Discord.Net.Rest; using System; using System.Collections.Generic; @@ -15,39 +17,37 @@ namespace Discord.Rest //TODO: Log Logins/Logouts public sealed class DiscordClient : IDiscordClient, IDisposable { - public event EventHandler Log; - public event EventHandler LoggedIn, LoggedOut; + public event Func Log; + public event Func LoggedIn, LoggedOut; private readonly Logger _discordLogger, _restLogger; private readonly SemaphoreSlim _connectionLock; private readonly RestClientProvider _restClientProvider; private readonly LogManager _log; - private CancellationTokenSource _cancelTokenSource; + private readonly RequestQueue _requestQueue; private bool _isDisposed; private SelfUser _currentUser; - public bool IsLoggedIn { get; private set; } + public LoginState LoginState { get; private set; } public API.DiscordApiClient ApiClient { get; private set; } - - public TokenType AuthTokenType => ApiClient.AuthTokenType; - public IRestClient RestClient => ApiClient.RestClient; - public IRequestQueue RequestQueue => ApiClient.RequestQueue; + + public IRequestQueue RequestQueue => _requestQueue; public DiscordClient(DiscordConfig config = null) { if (config == null) config = new DiscordConfig(); - - _restClientProvider = config.RestClientProvider; - + _log = new LogManager(config.LogLevel); - _log.Message += (s, e) => Log.Raise(this, e); + _log.Message += async e => await Log.Raise(e).ConfigureAwait(false); _discordLogger = _log.CreateLogger("Discord"); _restLogger = _log.CreateLogger("Rest"); _connectionLock = new SemaphoreSlim(1, 1); - ApiClient = new API.DiscordApiClient(_restClientProvider); - ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + _requestQueue = new RequestQueue(); + + ApiClient = new API.DiscordApiClient(config.RestClientProvider, requestQueue: _requestQueue); + ApiClient.SentRequest += async e => await _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms").ConfigureAwait(false); } public async Task Login(string email, string password) @@ -55,7 +55,7 @@ namespace Discord.Rest await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternal(email, password).ConfigureAwait(false); + await LoginInternal(TokenType.User, null, email, password, true, false).ConfigureAwait(false); } finally { _connectionLock.Release(); } } @@ -64,55 +64,51 @@ namespace Discord.Rest await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); + await LoginInternal(tokenType, token, null, null, false, validateToken).ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private async Task LoginInternal(string email, string password) + private async Task LoginInternal(TokenType tokenType, string token, string email, string password, bool useEmail, bool validateToken) { - if (IsLoggedIn) + if (LoginState != LoginState.LoggedOut) await LogoutInternal().ConfigureAwait(false); - try - { - _cancelTokenSource = new CancellationTokenSource(); + LoginState = LoginState.LoggingIn; - var args = new LoginParams { Email = email, Password = password }; - await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); - await CompleteLogin(false).ConfigureAwait(false); - } - catch { await LogoutInternal().ConfigureAwait(false); throw; } - } - private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) - { - if (IsLoggedIn) - await LogoutInternal().ConfigureAwait(false); try { - _cancelTokenSource = new CancellationTokenSource(); + if (useEmail) + { + var args = new LoginParams { Email = email, Password = password }; + await ApiClient.Login(args).ConfigureAwait(false); + } + else + await ApiClient.Login(tokenType, token).ConfigureAwait(false); - await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); - await CompleteLogin(validateToken).ConfigureAwait(false); - } - catch { await LogoutInternal().ConfigureAwait(false); throw; } - } - private async Task CompleteLogin(bool validateToken) - { - if (validateToken) - { - try + if (validateToken) { - await ApiClient.ValidateToken().ConfigureAwait(false); + try + { + await ApiClient.ValidateToken().ConfigureAwait(false); + } + catch (HttpException ex) + { + throw new ArgumentException("Token validation failed", nameof(token), ex); + } } - catch { await ApiClient.Logout().ConfigureAwait(false); } + + LoginState = LoginState.LoggedIn; } - - IsLoggedIn = true; - LoggedIn.Raise(this); + catch (Exception) + { + await LogoutInternal().ConfigureAwait(false); + throw; + } + + await LoggedIn.Raise().ConfigureAwait(false); } public async Task Logout() { - _cancelTokenSource?.Cancel(); await _connectionLock.WaitAsync().ConfigureAwait(false); try { @@ -122,22 +118,16 @@ namespace Discord.Rest } private async Task LogoutInternal() { - bool wasLoggedIn = IsLoggedIn; - - if (_cancelTokenSource != null) - { - try { _cancelTokenSource.Cancel(false); } - catch { } - } + if (LoginState == LoginState.LoggedOut) return; + LoginState = LoginState.LoggingOut; await ApiClient.Logout().ConfigureAwait(false); + _currentUser = null; - if (wasLoggedIn) - { - IsLoggedIn = false; - LoggedOut.Raise(this); - } + LoginState = LoginState.LoggedOut; + + await LoggedOut.Raise().ConfigureAwait(false); } public async Task> GetConnections() @@ -251,16 +241,15 @@ namespace Discord.Rest void Dispose(bool disposing) { if (!_isDisposed) - { - if (disposing) - _cancelTokenSource.Dispose(); _isDisposed = true; - } } public void Dispose() => Dispose(true); - API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; + ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; + WebSocket.Data.IDataStore IDiscordClient.DataStore => null; + Task IDiscordClient.Connect() { return Task.FromException(new NotSupportedException("This client does not support websocket connections.")); } + Task IDiscordClient.Disconnect() { return Task.FromException(new NotSupportedException("This client does not support websocket connections.")); } async Task IDiscordClient.GetChannel(ulong id) => await GetChannel(id).ConfigureAwait(false); async Task> IDiscordClient.GetDMChannels() diff --git a/src/Discord.Net/WebSocket/Data/DataStoreProvider.cs b/src/Discord.Net/WebSocket/Data/DataStoreProvider.cs new file mode 100644 index 000000000..0b1c78317 --- /dev/null +++ b/src/Discord.Net/WebSocket/Data/DataStoreProvider.cs @@ -0,0 +1,4 @@ +namespace Discord.WebSocket.Data +{ + public delegate IDataStore DataStoreProvider(int shardId, int totalShards, int guildCount, int dmCount); +} diff --git a/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs b/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs new file mode 100644 index 000000000..1308792a6 --- /dev/null +++ b/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs @@ -0,0 +1,110 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.WebSocket.Data +{ + public class DefaultDataStore : IDataStore + { + private const double AverageChannelsPerGuild = 10.22; //Source: Googie2149 + private const double AverageRolesPerGuild = 5; //Source: Googie2149 //TODO: Get a real value + private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 + private const double CollectionMultiplier = 1.05; //Add buffer to handle growth + private const double CollectionConcurrencyLevel = 1; //WebSocket updater/event handler. //TODO: Needs profiling, increase to 2? + + private ConcurrentDictionary _channels; + private ConcurrentDictionary _guilds; + private ConcurrentDictionary _roles; + private ConcurrentDictionary _users; + + public IEnumerable Channels => _channels.Select(x => x.Value); + public IEnumerable Guilds => _guilds.Select(x => x.Value); + public IEnumerable Roles => _roles.Select(x => x.Value); + public IEnumerable Users => _users.Select(x => x.Value); + + public DefaultDataStore(int guildCount, int dmChannelCount) + { + _channels = new ConcurrentDictionary(1, (int)((guildCount * AverageChannelsPerGuild + dmChannelCount) * CollectionMultiplier)); + _guilds = new ConcurrentDictionary(1, (int)(guildCount * CollectionMultiplier)); + _roles = new ConcurrentDictionary(1, (int)(guildCount * AverageRolesPerGuild * CollectionMultiplier)); + _users = new ConcurrentDictionary(1, (int)(guildCount * AverageUsersPerGuild * CollectionMultiplier)); + } + + public Channel GetChannel(ulong id) + { + Channel channel; + if (_channels.TryGetValue(id, out channel)) + return channel; + return null; + } + public void AddChannel(Channel channel) + { + _channels[channel.Id] = channel; + } + public Channel RemoveChannel(ulong id) + { + Channel channel; + if (_channels.TryRemove(id, out channel)) + return channel; + return null; + } + + public Guild GetGuild(ulong id) + { + Guild guild; + if (_guilds.TryGetValue(id, out guild)) + return guild; + return null; + } + public void AddGuild(Guild guild) + { + _guilds[guild.Id] = guild; + } + public Guild RemoveGuild(ulong id) + { + Guild guild; + if (_guilds.TryRemove(id, out guild)) + return guild; + return null; + } + + public Role GetRole(ulong id) + { + Role role; + if (_roles.TryGetValue(id, out role)) + return role; + return null; + } + public void AddRole(Role role) + { + _roles[role.Id] = role; + } + public Role RemoveRole(ulong id) + { + Role role; + if (_roles.TryRemove(id, out role)) + return role; + return null; + } + + public User GetUser(ulong id) + { + User user; + if (_users.TryGetValue(id, out user)) + return user; + return null; + } + public void AddUser(User user) + { + _users[user.Id] = user; + } + public User RemoveUser(ulong id) + { + User user; + if (_users.TryRemove(id, out user)) + return user; + return null; + } + } +} diff --git a/src/Discord.Net/WebSocket/Data/IDataStore.cs b/src/Discord.Net/WebSocket/Data/IDataStore.cs new file mode 100644 index 000000000..1a9d6e450 --- /dev/null +++ b/src/Discord.Net/WebSocket/Data/IDataStore.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Discord.WebSocket.Data +{ + public interface IDataStore + { + IEnumerable Channels { get; } + IEnumerable Guilds { get; } + IEnumerable Roles { get; } + IEnumerable Users { get; } + + Channel GetChannel(ulong id); + void AddChannel(Channel channel); + Channel RemoveChannel(ulong id); + + Guild GetGuild(ulong id); + void AddGuild(Guild guild); + Guild RemoveGuild(ulong id); + + Role GetRole(ulong id); + void AddRole(Role role); + Role RemoveRole(ulong id); + + User GetUser(ulong id); + void AddUser(User user); + User RemoveUser(ulong id); + } +} diff --git a/src/Discord.Net/WebSocket/Data/SharedDataStore.cs b/src/Discord.Net/WebSocket/Data/SharedDataStore.cs new file mode 100644 index 000000000..8512a2679 --- /dev/null +++ b/src/Discord.Net/WebSocket/Data/SharedDataStore.cs @@ -0,0 +1,7 @@ +namespace Discord.WebSocket.Data +{ + //TODO: Implement + /*public class SharedDataStore + { + }*/ +} diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 99fba0107..71b327a74 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -1,11 +1,19 @@ -using Discord.API.Rest; +using Discord.API; +using Discord.API.Gateway; +using Discord.API.Rest; using Discord.Logging; -using Discord.Net.Rest; +using Discord.Net; +using Discord.Net.Converters; +using Discord.Net.Queue; +using Discord.Net.WebSockets; +using Discord.WebSocket.Data; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,75 +22,67 @@ namespace Discord.WebSocket { //TODO: Docstrings //TODO: Log Logins/Logouts + //TODO: Do a final namespace and file structure review public sealed class DiscordClient : IDiscordClient, IDisposable { - public event EventHandler Log; - public event EventHandler LoggedIn, LoggedOut; - public event EventHandler Connected, Disconnected; - public event EventHandler VoiceConnected, VoiceDisconnected; - - public event EventHandler ChannelCreated, ChannelDestroyed; - public event EventHandler ChannelUpdated; - public event EventHandler MessageReceived, MessageDeleted; - public event EventHandler MessageUpdated; - public event EventHandler RoleCreated, RoleDeleted; - public event EventHandler RoleUpdated; - public event EventHandler JoinedGuild, LeftGuild; - public event EventHandler GuildAvailable, GuildUnavailable; - public event EventHandler GuildUpdated; - public event EventHandler CurrentUserUpdated; - public event EventHandler UserJoined, UserLeft; - public event EventHandler UserBanned, UserUnbanned; - public event EventHandler UserUpdated; - public event EventHandler UserIsTyping; + public event Func Log; + public event Func LoggedIn, LoggedOut; + public event Func Connected, Disconnected; + //public event Func VoiceConnected, VoiceDisconnected; + public event Func ChannelCreated, ChannelDestroyed; + public event Func ChannelUpdated; + public event Func MessageReceived, MessageDeleted; + public event Func MessageUpdated; + public event Func RoleCreated, RoleDeleted; + public event Func RoleUpdated; + public event Func JoinedGuild, LeftGuild, GuildAvailable, GuildUnavailable; + public event Func GuildUpdated; + public event Func UserJoined, UserLeft, UserBanned, UserUnbanned; + public event Func UserUpdated; + public event Func UserIsTyping; + private readonly ConcurrentQueue _largeGuilds; private readonly Logger _discordLogger, _gatewayLogger; private readonly SemaphoreSlim _connectionLock; - private readonly RestClientProvider _restClientProvider; + private readonly DataStoreProvider _dataStoreProvider; private readonly LogManager _log; + private readonly RequestQueue _requestQueue; + private readonly JsonSerializer _serializer; private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay; private readonly bool _enablePreUpdateEvents; private readonly int _largeThreshold; private readonly int _totalShards; - private IReadOnlyDictionary _voiceRegions; - private CancellationTokenSource _cancelTokenSource; + private ImmutableDictionary _voiceRegions; + private string _sessionId; private bool _isDisposed; - private SelfUser _currentUser; - private ConcurrentDictionary _guilds; - private ConcurrentDictionary _channels; - private ConcurrentDictionary _dmChannels; //Key = RecipientId - private ConcurrentDictionary _users; public int ShardId { get; } - public bool IsLoggedIn { get; private set; } + public LoginState LoginState { get; private set; } + public ConnectionState ConnectionState { get; private set; } public API.DiscordApiClient ApiClient { get; private set; } + public IWebSocketClient GatewaySocket { get; private set; } + public IDataStore DataStore { get; private set; } public SelfUser CurrentUser { get; private set; } - //public GatewaySocket GatewaySocket { get; private set; } internal int MessageCacheSize { get; private set; } internal bool UsePermissionCache { get; private set; } - - public TokenType AuthTokenType => ApiClient.AuthTokenType; - public IRestClient RestClient => ApiClient.RestClient; - public IRequestQueue RequestQueue => ApiClient.RequestQueue; - public IEnumerable Guilds => _guilds.Values; - public IEnumerable Channels => _channels.Values; - public IEnumerable DMChannels => _dmChannels.Values; - public IEnumerable VoiceRegions => _voiceRegions.Values; - - //public bool IsConnected => GatewaySocket.State == ConnectionState.Connected; + + public IRequestQueue RequestQueue => _requestQueue; + public IEnumerable Guilds => DataStore.Guilds; + public IEnumerable DMChannels => DataStore.Users.Select(x => x.DMChannel).Where(x => x != null); + public IEnumerable VoiceRegions => _voiceRegions.Select(x => x.Value); public DiscordClient(DiscordSocketConfig config = null) { if (config == null) config = new DiscordSocketConfig(); - - _restClientProvider = config.RestClientProvider; + ShardId = config.ShardId; _totalShards = config.TotalShards; _connectionTimeout = config.ConnectionTimeout; _reconnectDelay = config.ReconnectDelay; _failedReconnectDelay = config.FailedReconnectDelay; + _dataStoreProvider = config.DataStoreProvider; MessageCacheSize = config.MessageCacheSize; UsePermissionCache = config.UsePermissionsCache; @@ -90,26 +90,52 @@ namespace Discord.WebSocket _largeThreshold = config.LargeThreshold; _log = new LogManager(config.LogLevel); - _log.Message += (s, e) => Log.Raise(this, e); + _log.Message += async e => await Log.Raise(e).ConfigureAwait(false); _discordLogger = _log.CreateLogger("Discord"); _gatewayLogger = _log.CreateLogger("Gateway"); _connectionLock = new SemaphoreSlim(1, 1); - ApiClient = new API.DiscordApiClient(_restClientProvider); - ApiClient.SentRequest += (s, e) => _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + _requestQueue = new RequestQueue(); + _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + + ApiClient = new API.DiscordApiClient(config.RestClientProvider, config.WebSocketProvider, _serializer, _requestQueue); + ApiClient.SentRequest += async e => await _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + GatewaySocket = config.WebSocketProvider(); + GatewaySocket.BinaryMessage += async e => + { + using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2)) + using (var decompressed = new MemoryStream()) + { + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(decompressed); + decompressed.Position = 0; + using (var reader = new StreamReader(decompressed)) + await ProcessMessage(reader.ReadToEnd()).ConfigureAwait(false); + } + }; + GatewaySocket.TextMessage += async e => await ProcessMessage(e.Message).ConfigureAwait(false); - _channels = new ConcurrentDictionary(1, 100); - _dmChannels = new ConcurrentDictionary(1, 100); - _guilds = new ConcurrentDictionary(1, 25); - _users = new ConcurrentDictionary(1, 250); + _voiceRegions = ImmutableDictionary.Create(); + _largeGuilds = new ConcurrentQueue(); } + + void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + ApiClient?.Dispose(); + _isDisposed = true; + } + } + public void Dispose() => Dispose(true); public async Task Login(string email, string password) { await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternal(email, password).ConfigureAwait(false); + await LoginInternal(TokenType.User, null, email, password, true, false).ConfigureAwait(false); } finally { _connectionLock.Release(); } } @@ -118,87 +144,133 @@ namespace Discord.WebSocket await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternal(tokenType, token, validateToken).ConfigureAwait(false); + await LoginInternal(tokenType, token, null, null, false, validateToken).ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private async Task LoginInternal(string email, string password) + private async Task LoginInternal(TokenType tokenType, string token, string email, string password, bool useEmail, bool validateToken) { - if (IsLoggedIn) + if (LoginState != LoginState.LoggedOut) await LogoutInternal().ConfigureAwait(false); + LoginState = LoginState.LoggingIn; + try { - _cancelTokenSource = new CancellationTokenSource(); + if (useEmail) + { + var args = new LoginParams { Email = email, Password = password }; + await ApiClient.Login(args).ConfigureAwait(false); + } + else + await ApiClient.Login(tokenType, token).ConfigureAwait(false); + + if (validateToken) + { + try + { + await ApiClient.ValidateToken().ConfigureAwait(false); + var gateway = await ApiClient.GetGateway(); + var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); + _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); - var args = new LoginParams { Email = email, Password = password }; - await ApiClient.Login(args, _cancelTokenSource.Token).ConfigureAwait(false); - await CompleteLogin(false).ConfigureAwait(false); + await GatewaySocket.Connect(gateway.Url).ConfigureAwait(false); + } + catch (HttpException ex) + { + throw new ArgumentException("Token validation failed", nameof(token), ex); + } + } + + LoginState = LoginState.LoggedIn; + } + catch (Exception) + { + await LogoutInternal().ConfigureAwait(false); + throw; } - catch { await LogoutInternal().ConfigureAwait(false); throw; } + + await LoggedIn.Raise().ConfigureAwait(false); } - private async Task LoginInternal(TokenType tokenType, string token, bool validateToken) + + public async Task Logout() { - if (IsLoggedIn) - await LogoutInternal().ConfigureAwait(false); + await _connectionLock.WaitAsync().ConfigureAwait(false); try { - _cancelTokenSource = new CancellationTokenSource(); - - await ApiClient.Login(tokenType, token, _cancelTokenSource.Token).ConfigureAwait(false); - await CompleteLogin(validateToken).ConfigureAwait(false); + await LogoutInternal().ConfigureAwait(false); } - catch { await LogoutInternal().ConfigureAwait(false); throw; } + finally { _connectionLock.Release(); } } - private async Task CompleteLogin(bool validateToken) + private async Task LogoutInternal() { - if (validateToken) - { - try - { - await ApiClient.ValidateToken().ConfigureAwait(false); - var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); - _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); + if (LoginState == LoginState.LoggedOut) return; + LoginState = LoginState.LoggingOut; - } - catch { await ApiClient.Logout().ConfigureAwait(false); } - } + if (ConnectionState != ConnectionState.Disconnected) + await DisconnectInternal().ConfigureAwait(false); + + await ApiClient.Logout().ConfigureAwait(false); + + _voiceRegions = ImmutableDictionary.Create(); + CurrentUser = null; - IsLoggedIn = true; - LoggedIn.Raise(this); + LoginState = LoginState.LoggedOut; + + await LoggedOut.Raise().ConfigureAwait(false); } - public async Task Logout() + public async Task Connect() { - _cancelTokenSource?.Cancel(); await _connectionLock.WaitAsync().ConfigureAwait(false); try { - await LogoutInternal().ConfigureAwait(false); + await ConnectInternal().ConfigureAwait(false); } finally { _connectionLock.Release(); } } - private async Task LogoutInternal() + private async Task ConnectInternal() { - bool wasLoggedIn = IsLoggedIn; + if (LoginState != LoginState.LoggedIn) + throw new InvalidOperationException("You must log in before connecting."); - if (_cancelTokenSource != null) + ConnectionState = ConnectionState.Connecting; + try { - try { _cancelTokenSource.Cancel(false); } - catch { } + await ApiClient.Connect().ConfigureAwait(false); + + ConnectionState = ConnectionState.Connected; + } + catch (Exception) + { + await DisconnectInternal().ConfigureAwait(false); + throw; } - await ApiClient.Logout().ConfigureAwait(false); - _channels.Clear(); - _dmChannels.Clear(); - _guilds.Clear(); - _users.Clear(); - _currentUser = null; + await Connected.Raise().ConfigureAwait(false); + } - if (wasLoggedIn) + public async Task Disconnect() + { + await _connectionLock.WaitAsync().ConfigureAwait(false); + try { - IsLoggedIn = false; - LoggedOut.Raise(this); + await DisconnectInternal().ConfigureAwait(false); } + finally { _connectionLock.Release(); } + } + private async Task DisconnectInternal() + { + ulong guildId; + + if (ConnectionState == ConnectionState.Disconnected) return; + ConnectionState = ConnectionState.Disconnecting; + + await ApiClient.Disconnect().ConfigureAwait(false); + while (_largeGuilds.TryDequeue(out guildId)) { } + + ConnectionState = ConnectionState.Disconnected; + + await Disconnected.Raise().ConfigureAwait(false); } public async Task> GetConnections() @@ -207,12 +279,9 @@ namespace Discord.WebSocket return models.Select(x => new Connection(x)); } - public IChannel GetChannel(ulong id) + public async Task GetChannel(ulong id) { - IChannel channel; - if (_channels.TryGetValue(id, out channel)) - return channel; - return null; + return DataStore.GetChannel(id); } public async Task GetInvite(string inviteIdOrXkcd) @@ -223,12 +292,9 @@ namespace Discord.WebSocket return null; } - public Guild GetGuild(ulong id) + public async Task GetGuild(ulong id) { - Guild guild; - if (_guilds.TryGetValue(id, out guild)) - return guild; - return null; + return DataStore.GetGuild(id); } public async Task CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null) { @@ -237,16 +303,13 @@ namespace Discord.WebSocket return new Guild(this, model); } - public User GetUser(ulong id) + public async Task GetUser(ulong id) { - User user; - if (_users.TryGetValue(id, out user)) - return user; - return null; + return DataStore.GetUser(id); } - public User GetUser(string username, ushort discriminator) + public async Task GetUser(string username, ushort discriminator) { - return _users.Where(x => x.Value.Discriminator == discriminator && x.Value.Username == username).Select(x => x.Value).FirstOrDefault(); + return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); } public async Task> QueryUsers(string query, int limit) { @@ -254,7 +317,7 @@ namespace Discord.WebSocket return models.Select(x => new User(this, x)); } - public VoiceRegion GetVoiceRegion(string id) + public async Task GetVoiceRegion(string id) { VoiceRegion region; if (_voiceRegions.TryGetValue(id, out region)) @@ -262,44 +325,572 @@ namespace Discord.WebSocket return null; } - void Dispose(bool disposing) + private async Task ProcessMessage(string json) { - if (!_isDisposed) + var msg = JsonConvert.DeserializeObject(json); + try { - if (disposing) - _cancelTokenSource.Dispose(); - _isDisposed = true; + switch (msg.Type) + { + //Global + case "READY": + { + //TODO: None of this is really threadsafe - should only replace the cache collections when they have been fully populated + //TODO: Store guilds even if they're unavailable + //TODO: Make downloading large guilds optional + + var data = msg.Payload.ToObject(_serializer); + var store = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); + + _sessionId = data.SessionId; + var currentUser = new SelfUser(this, data.User); + store.AddUser(currentUser); + + for (int i = 0; i < data.Guilds.Length; i++) + { + var model = data.Guilds[i]; + var guild = new Guild(this, model); + store.AddGuild(guild); + + foreach (var channel in guild.Channels) + store.AddChannel(channel); + + /*if (model.IsLarge) + _largeGuilds.Enqueue(model.Id);*/ + } + + for (int i = 0; i < data.PrivateChannels.Length; i++) + { + var model = data.PrivateChannels[i]; + var recipient = new User(this, model.Recipient); + var channel = new DMChannel(this, recipient, model); + + recipient.DMChannel = channel; + store.AddChannel(channel); + } + + CurrentUser = currentUser; + DataStore = store; + } + break; + + //Servers + case "GUILD_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); + if (data.Unavailable != true) + { + var server = AddServer(data.Id); + server.Update(data); + + if (data.Unavailable != false) + { + _gatewayLogger.Info($"GUILD_CREATE: {server.Path}"); + JoinedServer.Raise(server); + } + else + _gatewayLogger.Info($"GUILD_AVAILABLE: {server.Path}"); + + if (!data.IsLarge) + await GuildAvailable.Raise(server); + else + _largeServers.Enqueue(data.Id); + }*/ + } + break; + case "GUILD_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.Id); + if (server != null) + { + var before = Config.EnablePreUpdateEvents ? server.Clone() : null; + server.Update(data); + _gatewayLogger.Info($"GUILD_UPDATE: {server.Path}"); + await GuildUpdated.Raise(before, server); + } + else + _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");*/ + } + break; + case "GUILD_DELETE": + { + /*var data = msg.Payload.ToObject(Serializer); + Server server = RemoveServer(data.Id); + if (server != null) + { + if (data.Unavailable != true) + _gatewayLogger.Info($"GUILD_DELETE: {server.Path}"); + else + _gatewayLogger.Info($"GUILD_UNAVAILABLE: {server.Path}"); + + OnServerUnavailable(server); + if (data.Unavailable != true) + OnLeftServer(server); + } + else + _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild.");*/ + } + break; + + //Channels + case "CHANNEL_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); + + Channel channel = null; + if (data.GuildId != null) + { + var server = GetServer(data.GuildId.Value); + if (server != null) + channel = server.AddChannel(data.Id, true); + else + _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild."); + } + else + channel = AddPrivateChannel(data.Id, data.Recipient.Id); + if (channel != null) + { + channel.Update(data); + _gatewayLogger.Info($"CHANNEL_CREATE: {channel.Path}"); + ChannelCreated.Raise(new ChannelEventArgs(channel)); + }*/ + } + break; + case "CHANNEL_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.Id); + if (channel != null) + { + var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; + channel.Update(data); + _gateway_gatewayLogger.Info($"CHANNEL_UPDATE: {channel.Path}"); + OnChannelUpdated(before, channel); + } + else + _gateway_gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");*/ + } + break; + case "CHANNEL_DELETE": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = RemoveChannel(data.Id); + if (channel != null) + { + _gateway_gatewayLogger.Info($"CHANNEL_DELETE: {channel.Path}"); + OnChannelDestroyed(channel); + } + else + _gateway_gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");*/ + } + break; + + //Members + case "GUILD_MEMBER_ADD": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.AddUser(data.User.Id, true, true); + user.Update(data); + user.UpdateActivity(); + _gatewayLogger.Info($"GUILD_MEMBER_ADD: {user.Path}"); + OnUserJoined(user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");*/ + } + break; + case "GUILD_MEMBER_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + _gatewayLogger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); + OnUserUpdated(before, user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");*/ + } + break; + case "GUILD_MEMBER_REMOVE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.RemoveUser(data.User.Id); + if (user != null) + { + _gatewayLogger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); + OnUserLeft(user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");*/ + } + break; + case "GUILD_MEMBERS_CHUNK": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + foreach (var memberData in data.Members) + { + var user = server.AddUser(memberData.User.Id, true, false); + user.Update(memberData); + } + _gateway_gatewayLogger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); + + if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there + OnServerAvailable(server); + } + else + _gateway_gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");*/ + } + break; + + //Roles + case "GUILD_ROLE_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.AddRole(data.Data.Id); + role.Update(data.Data, false); + _gateway_gatewayLogger.Info($"GUILD_ROLE_CREATE: {role.Path}"); + OnRoleCreated(role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");*/ + } + break; + case "GUILD_ROLE_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.GetRole(data.Data.Id); + if (role != null) + { + var before = Config.EnablePreUpdateEvents ? role.Clone() : null; + role.Update(data.Data, true); + _gateway_gatewayLogger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); + OnRoleUpdated(before, role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");*/ + } + break; + case "GUILD_ROLE_DELETE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.RemoveRole(data.RoleId); + if (role != null) + { + _gateway_gatewayLogger.Info($"GUILD_ROLE_DELETE: {role.Path}"); + OnRoleDeleted(role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");*/ + } + break; + + //Bans + case "GUILD_BAN_ADD": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + _gateway_gatewayLogger.Info($"GUILD_BAN_ADD: {user.Path}"); + OnUserBanned(user); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown user."); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");*/ + } + break; + case "GUILD_BAN_REMOVE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = new User(this, data.User.Id, server); + user.Update(data.User); + _gateway_gatewayLogger.Info($"GUILD_BAN_REMOVE: {user.Path}"); + OnUserUnbanned(user); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");*/ + } + break; + + //Messages + case "MESSAGE_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); + + Channel channel = GetChannel(data.ChannelId); + if (channel != null) + { + var user = channel.GetUserFast(data.Author.Id); + + if (user != null) + { + Message msg = null; + bool isAuthor = data.Author.Id == CurrentUser.Id; + //ulong nonce = 0; + + //if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) + //{ + // if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) + // msg = _messages[nonce]; + //} + if (msg == null) + { + msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); + //nonce = 0; + } + + //Remapped queued message + //if (nonce != 0) + //{ + // msg = _messages.Remap(nonce, data.Id); + // msg.Id = data.Id; + // RaiseMessageSent(msg); + //} + + msg.Update(data); + user.UpdateActivity(); + + _gateway_gatewayLogger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); + OnMessageReceived(msg); + } + else + _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user."); + } + else + _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");*/ + } + break; + case "MESSAGE_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.GetMessage(data.Id, data.Author?.Id); + var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; + msg.Update(data); + _gatewayLogger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); + OnMessageUpdated(before, msg); + } + else + _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");*/ + } + break; + case "MESSAGE_DELETE": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.RemoveMessage(data.Id); + _gatewayLogger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); + OnMessageDeleted(msg); + } + else + _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");*/ + } + break; + + //Statuses + case "PRESENCE_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + User user; + Server server; + if (data.GuildId == null) + { + server = null; + user = GetPrivateChannel(data.User.Id)?.Recipient; + } + else + { + server = GetServer(data.GuildId.Value); + if (server == null) + { + _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown server."); + break; + } + else + user = server.GetUser(data.User.Id); + } + + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"PRESENCE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + OnUserUpdated(before, user); + } + //else //Occurs when a user leaves a server + // _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ + } + break; + case "TYPING_START": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + User user; + if (channel.IsPrivate) + { + if (channel.Recipient.Id == data.UserId) + user = channel.Recipient; + else + break; + } + else + user = channel.Server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); + OnUserIsTypingUpdated(channel, user); + user.UpdateActivity(); + } + } + else + _gatewayLogger.Warning("TYPING_START referenced an unknown channel.");*/ + } + break; + + //Voice + case "VOICE_STATE_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var user = server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + //_gatewayLogger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); + OnUserUpdated(before, user); + } + //else //Occurs when a user leaves a server + // _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user."); + } + else + _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown server.");*/ + } + break; + + //Settings + case "USER_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + if (data.Id == CurrentUser.Id) + { + var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; + CurrentUser.Update(data); + foreach (var server in _servers) + server.Value.CurrentUser.Update(data); + _gatewayLogger.Info($"USER_UPDATE"); + OnProfileUpdated(before, CurrentUser); + }*/ + } + break; + + //Handled in GatewaySocket + case "RESUMED": + break; + + //Ignored + case "USER_SETTINGS_UPDATE": + case "MESSAGE_ACK": //TODO: Add (User only) + case "GUILD_EMOJIS_UPDATE": //TODO: Add + case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add + case "VOICE_SERVER_UPDATE": //TODO: Add + _gatewayLogger.Debug($"{msg.Type} [Ignored]"); + break; + + //Others + default: + _gatewayLogger.Warning($"Unknown message type: {msg.Type}"); + break; + } + } + catch (Exception ex) + { + _gatewayLogger.Error($"Error handling {msg.Type} event", ex); } } - public void Dispose() => Dispose(true); - - API.DiscordApiClient IDiscordClient.ApiClient => ApiClient; - Task IDiscordClient.GetChannel(ulong id) - => Task.FromResult(GetChannel(id)); + async Task IDiscordClient.GetChannel(ulong id) + => await GetChannel(id).ConfigureAwait(false); Task> IDiscordClient.GetDMChannels() - => Task.FromResult>(DMChannels); + => Task.FromResult>(DMChannels.ToImmutableArray()); async Task> IDiscordClient.GetConnections() => await GetConnections().ConfigureAwait(false); async Task IDiscordClient.GetInvite(string inviteIdOrXkcd) => await GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - Task IDiscordClient.GetGuild(ulong id) - => Task.FromResult(GetGuild(id)); + async Task IDiscordClient.GetGuild(ulong id) + => await GetGuild(id).ConfigureAwait(false); Task> IDiscordClient.GetGuilds() - => Task.FromResult>(Guilds); + => Task.FromResult>(Guilds.ToImmutableArray()); async Task IDiscordClient.CreateGuild(string name, IVoiceRegion region, Stream jpegIcon) => await CreateGuild(name, region, jpegIcon).ConfigureAwait(false); - Task IDiscordClient.GetUser(ulong id) - => Task.FromResult(GetUser(id)); - Task IDiscordClient.GetUser(string username, ushort discriminator) - => Task.FromResult(GetUser(username, discriminator)); + async Task IDiscordClient.GetUser(ulong id) + => await GetUser(id).ConfigureAwait(false); + async Task IDiscordClient.GetUser(string username, ushort discriminator) + => await GetUser(username, discriminator).ConfigureAwait(false); Task IDiscordClient.GetCurrentUser() => Task.FromResult(CurrentUser); async Task> IDiscordClient.QueryUsers(string query, int limit) => await QueryUsers(query, limit).ConfigureAwait(false); Task> IDiscordClient.GetVoiceRegions() - => Task.FromResult>(VoiceRegions); - Task IDiscordClient.GetVoiceRegion(string id) - => Task.FromResult(GetVoiceRegion(id)); + => Task.FromResult>(VoiceRegions.ToImmutableArray()); + async Task IDiscordClient.GetVoiceRegion(string id) + => await GetVoiceRegion(id).ConfigureAwait(false); } } diff --git a/src/Discord.Net/WebSocket/DiscordSocketConfig.cs b/src/Discord.Net/WebSocket/DiscordSocketConfig.cs index abe76a0d7..4318bd247 100644 --- a/src/Discord.Net/WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net/WebSocket/DiscordSocketConfig.cs @@ -1,4 +1,5 @@ using Discord.Net.WebSockets; +using Discord.WebSocket.Data; namespace Discord.WebSocket { @@ -32,8 +33,10 @@ namespace Discord.WebSocket public int LargeThreshold { get; set; } = 250; //Engines - + + /// Gets or sets the provider used to generate datastores. + public DataStoreProvider DataStoreProvider { get; set; } = (shardId, totalShards, guildCount, dmCount) => new DefaultDataStore(guildCount, dmCount); /// Gets or sets the provider used to generate new websocket connections. - public WebSocketProvider WebSocketProvider { get; set; } = null; + public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient(); } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index 1101dd961..a69f3e5f4 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -23,7 +23,7 @@ namespace Discord.WebSocket /// public string Username { get; private set; } /// - public DMChannel DMChannel { get; private set; } + public DMChannel DMChannel { get; internal set; } /// public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); @@ -58,7 +58,6 @@ namespace Discord.WebSocket var model = await Discord.ApiClient.CreateDMChannel(args).ConfigureAwait(false); channel = new DMChannel(Discord, this, model); - DMChannel = channel; } return channel; } diff --git a/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs b/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs deleted file mode 100644 index 4689e44bc..000000000 --- a/src/Discord.Net/WebSocket/Events/ChannelEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class ChannelEventArgs : EventArgs - { - public IChannel Channel { get; } - - public ChannelEventArgs(IChannel channel) - { - Channel = channel; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs deleted file mode 100644 index 24d001bc3..000000000 --- a/src/Discord.Net/WebSocket/Events/ChannelUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class ChannelUpdatedEventArgs : ChannelEventArgs - { - public IChannel Before { get; } - public IChannel After => Channel; - - public ChannelUpdatedEventArgs(IChannel before, IChannel after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs b/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs deleted file mode 100644 index 97d352c31..000000000 --- a/src/Discord.Net/WebSocket/Events/CurrentUserEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class CurrentUserEventArgs : EventArgs - { - public SelfUser CurrentUser { get; } - - public CurrentUserEventArgs(SelfUser currentUser) - { - CurrentUser = currentUser; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs deleted file mode 100644 index 167278089..000000000 --- a/src/Discord.Net/WebSocket/Events/CurrentUserUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class CurrentUserUpdatedEventArgs : CurrentUserEventArgs - { - public SelfUser Before { get; } - public SelfUser After => CurrentUser; - - public CurrentUserUpdatedEventArgs(SelfUser before, SelfUser after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs b/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs deleted file mode 100644 index d9120b243..000000000 --- a/src/Discord.Net/WebSocket/Events/DisconnectedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class DisconnectedEventArgs : EventArgs - { - public bool WasUnexpected { get; } - public Exception Exception { get; } - - public DisconnectedEventArgs(bool wasUnexpected, Exception exception = null) - { - WasUnexpected = wasUnexpected; - Exception = exception; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs b/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs deleted file mode 100644 index 8c9d4dc20..000000000 --- a/src/Discord.Net/WebSocket/Events/GuildEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class GuildEventArgs : EventArgs - { - public Guild Guild { get; } - - public GuildEventArgs(Guild guild) - { - Guild = guild; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs deleted file mode 100644 index 03a7f0f75..000000000 --- a/src/Discord.Net/WebSocket/Events/GuildUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class GuildUpdatedEventArgs : GuildEventArgs - { - public Guild Before { get; } - public Guild After => Guild; - - public GuildUpdatedEventArgs(Guild before, Guild after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs b/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs deleted file mode 100644 index a1bea179a..000000000 --- a/src/Discord.Net/WebSocket/Events/MessageEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class MessageEventArgs : EventArgs - { - public Message Message { get; } - - public MessageEventArgs(Message message) - { - Message = message; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs deleted file mode 100644 index 14b41707d..000000000 --- a/src/Discord.Net/WebSocket/Events/MessageUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class MessageUpdatedEventArgs : MessageEventArgs - { - public Message Before { get; } - public Message After => Message; - - public MessageUpdatedEventArgs(Message before, Message after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs b/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs deleted file mode 100644 index 902eea1e8..000000000 --- a/src/Discord.Net/WebSocket/Events/RoleEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class RoleEventArgs : EventArgs - { - public Role Role { get; } - - public RoleEventArgs(Role role) - { - Role = role; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs deleted file mode 100644 index 45c6f05a9..000000000 --- a/src/Discord.Net/WebSocket/Events/RoleUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class RoleUpdatedEventArgs : RoleEventArgs - { - public Role Before { get; } - public Role After => Role; - - public RoleUpdatedEventArgs(Role before, Role after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs b/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs deleted file mode 100644 index a3f45d452..000000000 --- a/src/Discord.Net/WebSocket/Events/TypingEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class TypingEventArgs : EventArgs - { - public IMessageChannel Channel { get; } - public IUser User { get; } - - public TypingEventArgs(IMessageChannel channel, IUser user) - { - Channel = channel; - User = user; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/UserEventArgs.cs b/src/Discord.Net/WebSocket/Events/UserEventArgs.cs deleted file mode 100644 index 8e31a58aa..000000000 --- a/src/Discord.Net/WebSocket/Events/UserEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class UserEventArgs : EventArgs - { - public IUser User { get; } - - public UserEventArgs(IUser user) - { - User = user; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs b/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs deleted file mode 100644 index 298b0d427..000000000 --- a/src/Discord.Net/WebSocket/Events/UserUpdatedEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Discord.WebSocket -{ - public class UserUpdatedEventArgs : UserEventArgs - { - public IUser Before { get; } - public IUser After => User; - - public UserUpdatedEventArgs(IUser before, IUser after) - : base(after) - { - Before = before; - } - } -} diff --git a/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs b/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs deleted file mode 100644 index 1b10f3cd7..000000000 --- a/src/Discord.Net/WebSocket/Events/VoiceChannelEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Discord.WebSocket -{ - public class VoiceChannelEventArgs : EventArgs - { - public VoiceChannel Channel { get; } - - public VoiceChannelEventArgs(VoiceChannel channel) - { - Channel = channel; - } - } -} diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 01b6bd4dc..a15ef430a 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,6 +1,6 @@ -{ +{ "version": "1.0.0-dev", - "description": "A Discord.Net extension adding voice support.", + "description": "An unofficial .Net API wrapper for the Discord service.", "authors": [ "RogueException" ], "packOptions": { From 5f43369220cb720534a90eb247b7d7c6a56e92cb Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 02:18:29 -0300 Subject: [PATCH 34/39] Fixed several websocket and datastore issues --- src/Discord.Net/Net/Queue/RestRequest.cs | 4 ++- src/Discord.Net/Net/Rest/DefaultRestClient.cs | 4 ++- .../Net/WebSockets/DefaultWebsocketClient.cs | 9 +++++- src/Discord.Net/WebSocket/DiscordClient.cs | 31 +++++++++---------- .../WebSocket/Entities/Guilds/Guild.cs | 2 +- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net/Net/Queue/RestRequest.cs b/src/Discord.Net/Net/Queue/RestRequest.cs index 49c557dfd..179ce6423 100644 --- a/src/Discord.Net/Net/Queue/RestRequest.cs +++ b/src/Discord.Net/Net/Queue/RestRequest.cs @@ -46,8 +46,10 @@ namespace Discord.Net.Queue { if (IsMultipart) return await Client.Send(Method, Endpoint, MultipartParams, HeaderOnly).ConfigureAwait(false); - else + else if (Json != null) return await Client.Send(Method, Endpoint, Json, HeaderOnly).ConfigureAwait(false); + else + return await Client.Send(Method, Endpoint, HeaderOnly).ConfigureAwait(false); } } } diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net/Net/Rest/DefaultRestClient.cs index 8166f23a7..9d83e9a32 100644 --- a/src/Discord.Net/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net/Net/Rest/DefaultRestClient.cs @@ -32,8 +32,10 @@ namespace Discord.Net.Rest UseProxy = false, PreAuthenticate = false }); - SetHeader("accept-encoding", "gzip, deflate"); + + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationToken.None; _parentToken = CancellationToken.None; } protected virtual void Dispose(bool disposing) diff --git a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs index 8eee70c0c..e9ae38f55 100644 --- a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs +++ b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs @@ -28,6 +28,9 @@ namespace Discord.Net.WebSockets _client = new ClientWebSocket(); _client.Options.Proxy = null; _client.Options.KeepAliveInterval = TimeSpan.Zero; + + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationToken.None; _parentToken = CancellationToken.None; } private void Dispose(bool disposing) @@ -46,6 +49,7 @@ namespace Discord.Net.WebSockets public async Task Connect(string host) { + //Assume locked await Disconnect().ConfigureAwait(false); _cancelTokenSource = new CancellationTokenSource(); @@ -56,9 +60,11 @@ namespace Discord.Net.WebSockets } public async Task Disconnect() { + //Assume locked _cancelTokenSource.Cancel(); - _client.Abort(); + if (_client.State == WebSocketState.Open) + try { await _client?.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } catch { } await (_task ?? Task.CompletedTask).ConfigureAwait(false); } @@ -75,6 +81,7 @@ namespace Discord.Net.WebSockets public async Task Send(byte[] data, int offset, int count, bool isText) { + //TODO: If connection is temporarily down, retry? int frameCount = (int)Math.Ceiling((double)count / SendChunkSize); for (int i = 0; i < frameCount; i++, offset += SendChunkSize) diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 71b327a74..2639c9389 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -279,7 +279,7 @@ namespace Discord.WebSocket return models.Select(x => new Connection(x)); } - public async Task GetChannel(ulong id) + public Channel GetChannel(ulong id) { return DataStore.GetChannel(id); } @@ -292,7 +292,7 @@ namespace Discord.WebSocket return null; } - public async Task GetGuild(ulong id) + public Guild GetGuild(ulong id) { return DataStore.GetGuild(id); } @@ -303,11 +303,11 @@ namespace Discord.WebSocket return new Guild(this, model); } - public async Task GetUser(ulong id) + public User GetUser(ulong id) { return DataStore.GetUser(id); } - public async Task GetUser(string username, ushort discriminator) + public User GetUser(string username, ushort discriminator) { return DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault(); } @@ -317,7 +317,7 @@ namespace Discord.WebSocket return models.Select(x => new User(this, x)); } - public async Task GetVoiceRegion(string id) + public VoiceRegion GetVoiceRegion(string id) { VoiceRegion region; if (_voiceRegions.TryGetValue(id, out region)) @@ -335,7 +335,6 @@ namespace Discord.WebSocket //Global case "READY": { - //TODO: None of this is really threadsafe - should only replace the cache collections when they have been fully populated //TODO: Store guilds even if they're unavailable //TODO: Make downloading large guilds optional @@ -866,31 +865,31 @@ namespace Discord.WebSocket } } - async Task IDiscordClient.GetChannel(ulong id) - => await GetChannel(id).ConfigureAwait(false); + Task IDiscordClient.GetChannel(ulong id) + => Task.FromResult(GetChannel(id)); Task> IDiscordClient.GetDMChannels() => Task.FromResult>(DMChannels.ToImmutableArray()); async Task> IDiscordClient.GetConnections() => await GetConnections().ConfigureAwait(false); async Task IDiscordClient.GetInvite(string inviteIdOrXkcd) => await GetInvite(inviteIdOrXkcd).ConfigureAwait(false); - async Task IDiscordClient.GetGuild(ulong id) - => await GetGuild(id).ConfigureAwait(false); + Task IDiscordClient.GetGuild(ulong id) + => Task.FromResult(GetGuild(id)); Task> IDiscordClient.GetGuilds() => Task.FromResult>(Guilds.ToImmutableArray()); async Task IDiscordClient.CreateGuild(string name, IVoiceRegion region, Stream jpegIcon) => await CreateGuild(name, region, jpegIcon).ConfigureAwait(false); - async Task IDiscordClient.GetUser(ulong id) - => await GetUser(id).ConfigureAwait(false); - async Task IDiscordClient.GetUser(string username, ushort discriminator) - => await GetUser(username, discriminator).ConfigureAwait(false); + Task IDiscordClient.GetUser(ulong id) + => Task.FromResult(GetUser(id)); + Task IDiscordClient.GetUser(string username, ushort discriminator) + => Task.FromResult(GetUser(username, discriminator)); Task IDiscordClient.GetCurrentUser() => Task.FromResult(CurrentUser); async Task> IDiscordClient.QueryUsers(string query, int limit) => await QueryUsers(query, limit).ConfigureAwait(false); Task> IDiscordClient.GetVoiceRegions() => Task.FromResult>(VoiceRegions.ToImmutableArray()); - async Task IDiscordClient.GetVoiceRegion(string id) - => await GetVoiceRegion(id).ConfigureAwait(false); + Task IDiscordClient.GetVoiceRegion(string id) + => Task.FromResult(GetVoiceRegion(id)); } } diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 1b0f87f2a..19657d9bb 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -88,7 +88,7 @@ namespace Discord.WebSocket Update(model); } - private void Update(Model model) + private async void Update(Model model) { _afkChannelId = model.AFKChannelId; AFKTimeout = model.AFKTimeout; From 28247b17d0cf0c683e2f7894a577501499391d99 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 05:15:32 -0300 Subject: [PATCH 35/39] Lots more websocket and logging bugfixes :D --- src/Discord.Net/API/DiscordAPIClient.cs | 96 +- src/Discord.Net/API/DiscordAPISocketClient.cs | 11 - .../Gateway/{OpCodes.cs => GatewayOpCodes.cs} | 2 +- src/Discord.Net/API/Gateway/IdentifyParams.cs | 2 +- .../API/Gateway/RequestMembersParams.cs | 2 +- src/Discord.Net/API/Gateway/ResumeParams.cs | 2 +- .../API/Gateway/UpdateStatusParams.cs | 2 +- .../API/Gateway/UpdateVoiceParams.cs | 2 +- src/Discord.Net/API/Voice/VoiceOpCodes.cs | 18 + src/Discord.Net/API/WebSocketMessage.cs | 5 +- src/Discord.Net/DiscordConfig.cs | 3 +- src/Discord.Net/EventExtensions.cs | 3 +- .../Events/SentRequestEventArgs.cs | 20 - src/Discord.Net/Logging/LogManager.cs | 14 +- .../LogMessage.cs} | 4 +- src/Discord.Net/Logging/Logger.cs | 35 +- src/Discord.Net/Net/Queue/RequestQueue.cs | 28 +- src/Discord.Net/Net/Queue/RestRequest.cs | 2 +- src/Discord.Net/Net/Queue/WebSocketRequest.cs | 17 +- .../Net/WebSockets/BinaryMessageEventArgs.cs | 11 - .../Net/WebSockets/DefaultWebsocketClient.cs | 14 +- .../Net/WebSockets/IWebSocketClient.cs | 7 +- .../Net/WebSockets/TextMessageEventArgs.cs | 11 - src/Discord.Net/Rest/DiscordClient.cs | 6 +- src/Discord.Net/WebSocket/DiscordClient.cs | 997 +++++++++--------- .../WebSocket/Entities/Users/User.cs | 9 +- 26 files changed, 678 insertions(+), 645 deletions(-) delete mode 100644 src/Discord.Net/API/DiscordAPISocketClient.cs rename src/Discord.Net/API/Gateway/{OpCodes.cs => GatewayOpCodes.cs} (96%) create mode 100644 src/Discord.Net/API/Voice/VoiceOpCodes.cs delete mode 100644 src/Discord.Net/Events/SentRequestEventArgs.cs rename src/Discord.Net/{Events/LogMessageEventArgs.cs => Logging/LogMessage.cs} (91%) delete mode 100644 src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs delete mode 100644 src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs diff --git a/src/Discord.Net/API/DiscordAPIClient.cs b/src/Discord.Net/API/DiscordAPIClient.cs index 2f285030d..dee9fddf0 100644 --- a/src/Discord.Net/API/DiscordAPIClient.cs +++ b/src/Discord.Net/API/DiscordAPIClient.cs @@ -1,16 +1,19 @@ -using Discord.API.Rest; +using Discord.API.Gateway; +using Discord.API.Rest; using Discord.Net; using Discord.Net.Converters; using Discord.Net.Queue; using Discord.Net.Rest; using Discord.Net.WebSockets; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net; using System.Text; @@ -21,14 +24,17 @@ namespace Discord.API { public class DiscordApiClient : IDisposable { - internal event Func SentRequest; - + public event Func SentRequest; + public event Func SentGatewayMessage; + public event Func ReceivedGatewayEvent; + private readonly RequestQueue _requestQueue; private readonly JsonSerializer _serializer; private readonly IRestClient _restClient; private readonly IWebSocketClient _gatewayClient; private readonly SemaphoreSlim _connectionLock; private CancellationTokenSource _loginCancelToken, _connectCancelToken; + private string _authToken; private bool _isDisposed; public LoginState LoginState { get; private set; } @@ -48,6 +54,26 @@ namespace Discord.API { _gatewayClient = webSocketProvider(); _gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); + _gatewayClient.BinaryMessage += async (data, index, count) => + { + using (var compressed = new MemoryStream(data, index + 2, count - 2)) + using (var decompressed = new MemoryStream()) + { + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(decompressed); + decompressed.Position = 0; + using (var reader = new StreamReader(decompressed)) + { + var msg = JsonConvert.DeserializeObject(reader.ReadToEnd()); + await ReceivedGatewayEvent.Raise((GatewayOpCodes)msg.Operation, msg.Type, msg.Payload as JToken).ConfigureAwait(false); + } + } + }; + _gatewayClient.TextMessage += async text => + { + var msg = JsonConvert.DeserializeObject(text); + await ReceivedGatewayEvent.Raise((GatewayOpCodes)msg.Operation, msg.Type, msg.Payload as JToken).ConfigureAwait(false); + }; } _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; @@ -95,6 +121,7 @@ namespace Discord.API _loginCancelToken = new CancellationTokenSource(); AuthTokenType = TokenType.User; + _authToken = null; _restClient.SetHeader("authorization", null); await _requestQueue.SetCancelToken(_loginCancelToken.Token).ConfigureAwait(false); _restClient.SetCancelToken(_loginCancelToken.Token); @@ -106,6 +133,7 @@ namespace Discord.API } AuthTokenType = tokenType; + _authToken = token; switch (tokenType) { case TokenType.Bot: @@ -181,7 +209,10 @@ namespace Discord.API _gatewayClient.SetCancelToken(_connectCancelToken.Token); var gatewayResponse = await GetGateway().ConfigureAwait(false); - await _gatewayClient.Connect(gatewayResponse.Url).ConfigureAwait(false); + var url = $"{gatewayResponse.Url}?v={DiscordConfig.GatewayAPIVersion}&encoding={DiscordConfig.GatewayEncoding}"; + await _gatewayClient.Connect(url).ConfigureAwait(false); + + await SendIdentify().ConfigureAwait(false); ConnectionState = ConnectionState.Connected; } @@ -226,13 +257,13 @@ namespace Discord.API => SendInternal(method, endpoint, multipartArgs, true, bucket); public async Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, null, false, bucket).ConfigureAwait(false)); + => DeserializeJson(await SendInternal(method, endpoint, null, false, bucket).ConfigureAwait(false)); public async Task Send(string method, string endpoint, object payload, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, payload, false, bucket).ConfigureAwait(false)); + => DeserializeJson(await SendInternal(method, endpoint, payload, false, bucket).ConfigureAwait(false)); public async Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, multipartArgs, false, bucket).ConfigureAwait(false)); + => DeserializeJson(await SendInternal(method, endpoint, multipartArgs, false, bucket).ConfigureAwait(false)); public Task Send(string method, string endpoint, GuildBucket bucket, ulong guildId) => SendInternal(method, endpoint, null, true, bucket, guildId); @@ -242,14 +273,14 @@ namespace Discord.API => SendInternal(method, endpoint, multipartArgs, true, bucket, guildId); public async Task Send(string method, string endpoint, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, null, false, bucket, guildId).ConfigureAwait(false)); + => DeserializeJson(await SendInternal(method, endpoint, null, false, bucket, guildId).ConfigureAwait(false)); public async Task Send(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, payload, false, bucket, guildId).ConfigureAwait(false)); + => DeserializeJson(await SendInternal(method, endpoint, payload, false, bucket, guildId).ConfigureAwait(false)); public async Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, multipartArgs, false, bucket, guildId).ConfigureAwait(false)); - + => DeserializeJson(await SendInternal(method, endpoint, multipartArgs, false, bucket, guildId).ConfigureAwait(false)); + private Task SendInternal(string method, string endpoint, object payload, bool headerOnly, GlobalBucket bucket) => SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Global, (int)bucket, 0); private Task SendInternal(string method, string endpoint, object payload, bool headerOnly, GuildBucket bucket, ulong guildId) @@ -264,13 +295,12 @@ namespace Discord.API var stopwatch = Stopwatch.StartNew(); string json = null; if (payload != null) - json = Serialize(payload); + json = SerializeJson(payload); var responseStream = await _requestQueue.Send(new RestRequest(_restClient, method, endpoint, json, headerOnly), group, bucketId, guildId).ConfigureAwait(false); - int bytes = headerOnly ? 0 : (int)responseStream.Length; stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - await SentRequest.Raise(new SentRequestEventArgs(method, endpoint, bytes, milliseconds)).ConfigureAwait(false); + await SentRequest.Raise(method, endpoint, milliseconds).ConfigureAwait(false); return responseStream; } @@ -282,11 +312,28 @@ namespace Discord.API stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); - await SentRequest.Raise(new SentRequestEventArgs(method, endpoint, bytes, milliseconds)).ConfigureAwait(false); + await SentRequest.Raise(method, endpoint, milliseconds).ConfigureAwait(false); return responseStream; } + public Task SendGateway(GatewayOpCodes opCode, object payload, GlobalBucket bucket = GlobalBucket.Gateway) + => SendGateway((int)opCode, payload, BucketGroup.Global, (int)bucket, 0); + public Task SendGateway(VoiceOpCodes opCode, object payload, GlobalBucket bucket = GlobalBucket.Gateway) + => SendGateway((int)opCode, payload, BucketGroup.Global, (int)bucket, 0); + public Task SendGateway(GatewayOpCodes opCode, object payload, GuildBucket bucket, ulong guildId) + => SendGateway((int)opCode, payload, BucketGroup.Guild, (int)bucket, guildId); + public Task SendGateway(VoiceOpCodes opCode, object payload, GuildBucket bucket, ulong guildId) + => SendGateway((int)opCode, payload, BucketGroup.Guild, (int)bucket, guildId); + private async Task SendGateway(int opCode, object payload, BucketGroup group, int bucketId, ulong guildId) + { + //TODO: Add ETF + byte[] bytes = null; + payload = new WebSocketMessage { Operation = opCode, Payload = payload }; + if (payload != null) + bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); + await _requestQueue.Send(new WebSocketRequest(_gatewayClient, bytes, true), group, bucketId, guildId).ConfigureAwait(false); + } //Auth public async Task ValidateToken() @@ -299,6 +346,21 @@ namespace Discord.API { return await Send("GET", "gateway").ConfigureAwait(false); } + public async Task SendIdentify(int largeThreshold = 100, bool useCompression = true) + { + var props = new Dictionary + { + ["$device"] = "Discord.Net" + }; + var msg = new IdentifyParams() + { + Token = _authToken, + Properties = props, + LargeThreshold = largeThreshold, + UseCompression = useCompression + }; + await SendGateway(GatewayOpCodes.Identify, msg).ConfigureAwait(false); + } //Channels public async Task GetChannel(ulong channelId) @@ -986,7 +1048,7 @@ namespace Discord.API //Helpers private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - private string Serialize(object value) + private string SerializeJson(object value) { var sb = new StringBuilder(256); using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture)) @@ -994,7 +1056,7 @@ namespace Discord.API _serializer.Serialize(writer, value); return sb.ToString(); } - private T Deserialize(Stream jsonStream) + private T DeserializeJson(Stream jsonStream) { using (TextReader text = new StreamReader(jsonStream)) using (JsonReader reader = new JsonTextReader(text)) diff --git a/src/Discord.Net/API/DiscordAPISocketClient.cs b/src/Discord.Net/API/DiscordAPISocketClient.cs deleted file mode 100644 index fcff89923..000000000 --- a/src/Discord.Net/API/DiscordAPISocketClient.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.API -{ - public class DiscordAPISocketClient - { - } -} diff --git a/src/Discord.Net/API/Gateway/OpCodes.cs b/src/Discord.Net/API/Gateway/GatewayOpCodes.cs similarity index 96% rename from src/Discord.Net/API/Gateway/OpCodes.cs rename to src/Discord.Net/API/Gateway/GatewayOpCodes.cs index cf237bd03..82fbf51f3 100644 --- a/src/Discord.Net/API/Gateway/OpCodes.cs +++ b/src/Discord.Net/API/Gateway/GatewayOpCodes.cs @@ -1,6 +1,6 @@ namespace Discord.API.Gateway { - public enum OpCodes : byte + public enum GatewayOpCodes : byte { /// C←S - Used to send most events. Dispatch = 0, diff --git a/src/Discord.Net/API/Gateway/IdentifyParams.cs b/src/Discord.Net/API/Gateway/IdentifyParams.cs index 96c45927a..8338e6e14 100644 --- a/src/Discord.Net/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net/API/Gateway/IdentifyParams.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Discord.API.Gateway { - public class IdentifyCommand + public class IdentifyParams { [JsonProperty("token")] public string Token { get; set; } diff --git a/src/Discord.Net/API/Gateway/RequestMembersParams.cs b/src/Discord.Net/API/Gateway/RequestMembersParams.cs index 16077939d..ed6edc6ef 100644 --- a/src/Discord.Net/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net/API/Gateway/RequestMembersParams.cs @@ -2,7 +2,7 @@ namespace Discord.API.Gateway { - public class RequestMembersCommand + public class RequestMembersParams { [JsonProperty("guild_id")] public ulong[] GuildId { get; set; } diff --git a/src/Discord.Net/API/Gateway/ResumeParams.cs b/src/Discord.Net/API/Gateway/ResumeParams.cs index c0fb296f4..ba4489336 100644 --- a/src/Discord.Net/API/Gateway/ResumeParams.cs +++ b/src/Discord.Net/API/Gateway/ResumeParams.cs @@ -2,7 +2,7 @@ namespace Discord.API.Gateway { - public class ResumeCommand + public class ResumeParams { [JsonProperty("session_id")] public string SessionId { get; set; } diff --git a/src/Discord.Net/API/Gateway/UpdateStatusParams.cs b/src/Discord.Net/API/Gateway/UpdateStatusParams.cs index e673cb51e..99e5ed7b8 100644 --- a/src/Discord.Net/API/Gateway/UpdateStatusParams.cs +++ b/src/Discord.Net/API/Gateway/UpdateStatusParams.cs @@ -2,7 +2,7 @@ namespace Discord.API.Gateway { - public class UpdateStatusCommand + public class UpdateStatusParams { [JsonProperty("idle_since")] public long? IdleSince { get; set; } diff --git a/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs b/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs index 689394e5d..d72d63548 100644 --- a/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs +++ b/src/Discord.Net/API/Gateway/UpdateVoiceParams.cs @@ -2,7 +2,7 @@ namespace Discord.API.Gateway { - public class UpdateVoiceCommand + public class UpdateVoiceParams { [JsonProperty("guild_id")] public ulong? GuildId { get; set; } diff --git a/src/Discord.Net/API/Voice/VoiceOpCodes.cs b/src/Discord.Net/API/Voice/VoiceOpCodes.cs new file mode 100644 index 000000000..73c7eda0c --- /dev/null +++ b/src/Discord.Net/API/Voice/VoiceOpCodes.cs @@ -0,0 +1,18 @@ +namespace Discord.API.Gateway +{ + public enum VoiceOpCodes : byte + { + /// C→S - Used to associate a connection with a token. + Identify = 0, + /// C→S - Used to specify configuration. + SelectProtocol = 1, + /// C←S - Used to notify that the voice connection was successful and informs the client of available protocols. + Ready = 2, + /// C↔S - Used to keep the connection alive and measure latency. + Heartbeat = 3, + /// C←S - Used to provide an encryption key to the client. + SessionDescription = 4, + /// C↔S - Used to inform that a certain user is speaking. + Speaking = 5 + } +} diff --git a/src/Discord.Net/API/WebSocketMessage.cs b/src/Discord.Net/API/WebSocketMessage.cs index 285f5e13f..19ec2ac41 100644 --- a/src/Discord.Net/API/WebSocketMessage.cs +++ b/src/Discord.Net/API/WebSocketMessage.cs @@ -1,17 +1,16 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Discord.API { public class WebSocketMessage { [JsonProperty("op")] - public int? Operation { get; set; } + public int Operation { get; set; } [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] public uint? Sequence { get; set; } [JsonProperty("d")] - public JToken Payload { get; set; } + public object Payload { get; set; } } } diff --git a/src/Discord.Net/DiscordConfig.cs b/src/Discord.Net/DiscordConfig.cs index 50847dbb3..35e6f010b 100644 --- a/src/Discord.Net/DiscordConfig.cs +++ b/src/Discord.Net/DiscordConfig.cs @@ -10,7 +10,8 @@ namespace Discord public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly?.GetName().Version.ToString(3) ?? "Unknown"; public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; - public const int GatewayAPIVersion = 3; + public const int GatewayAPIVersion = 3; //TODO: Upgrade to 4 + public const string GatewayEncoding = "json"; public const string ClientAPIUrl = "https://discordapp.com/api/"; public const string CDNUrl = "https://cdn.discordapp.com/"; diff --git a/src/Discord.Net/EventExtensions.cs b/src/Discord.Net/EventExtensions.cs index f961866d1..b46cb9056 100644 --- a/src/Discord.Net/EventExtensions.cs +++ b/src/Discord.Net/EventExtensions.cs @@ -5,6 +5,7 @@ namespace Discord { internal static class EventExtensions { + //TODO: Optimize these for if there is only 1 subscriber (can we do this?) public static async Task Raise(this Func eventHandler) { var subscriptions = eventHandler?.GetInvocationList(); @@ -32,7 +33,7 @@ namespace Discord await (subscriptions[i] as Func).Invoke(arg1, arg2).ConfigureAwait(false); } } - public static async Task Raise(this Func eventHandler, T1 arg1, T2 arg2, T3 arg3) + public static async Task Raise(this Func eventHandler, T1 arg1, T2 arg2, T3 arg3) { var subscriptions = eventHandler?.GetInvocationList(); if (subscriptions != null) diff --git a/src/Discord.Net/Events/SentRequestEventArgs.cs b/src/Discord.Net/Events/SentRequestEventArgs.cs deleted file mode 100644 index c62c4d917..000000000 --- a/src/Discord.Net/Events/SentRequestEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Discord.Net.Rest -{ - public class SentRequestEventArgs : EventArgs - { - public string Method { get; } - public string Endpoint { get; } - public int ResponseLength { get; } - public double Milliseconds { get; } - - public SentRequestEventArgs(string method, string endpoint, int responseLength, double milliseconds) - { - Method = method; - Endpoint = endpoint; - ResponseLength = responseLength; - Milliseconds = milliseconds; - } - } -} diff --git a/src/Discord.Net/Logging/LogManager.cs b/src/Discord.Net/Logging/LogManager.cs index bcbfbd20a..5e5d819b7 100644 --- a/src/Discord.Net/Logging/LogManager.cs +++ b/src/Discord.Net/Logging/LogManager.cs @@ -7,7 +7,7 @@ namespace Discord.Logging { public LogSeverity Level { get; } - public event Func Message; + public event Func Message; internal LogManager(LogSeverity minSeverity) { @@ -17,32 +17,32 @@ namespace Discord.Logging public async Task Log(LogSeverity severity, string source, string message, Exception ex = null) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, source, message, ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); } public async Task Log(LogSeverity severity, string source, FormattableString message, Exception ex = null) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, source, message.ToString(), ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false); } public async Task Log(LogSeverity severity, string source, Exception ex) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, source, null, ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); } async Task ILogger.Log(LogSeverity severity, string message, Exception ex) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, "Discord", message, ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, "Discord", message, ex)).ConfigureAwait(false); } async Task ILogger.Log(LogSeverity severity, FormattableString message, Exception ex) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, "Discord", message.ToString(), ex)).ConfigureAwait(false); } async Task ILogger.Log(LogSeverity severity, Exception ex) { if (severity <= Level) - await Message.Raise(new LogMessageEventArgs(severity, "Discord", null, ex)).ConfigureAwait(false); + await Message.Raise(new LogMessage(severity, "Discord", null, ex)).ConfigureAwait(false); } public Task Error(string source, string message, Exception ex = null) diff --git a/src/Discord.Net/Events/LogMessageEventArgs.cs b/src/Discord.Net/Logging/LogMessage.cs similarity index 91% rename from src/Discord.Net/Events/LogMessageEventArgs.cs rename to src/Discord.Net/Logging/LogMessage.cs index dd8c885ee..14bc4e263 100644 --- a/src/Discord.Net/Events/LogMessageEventArgs.cs +++ b/src/Discord.Net/Logging/LogMessage.cs @@ -3,14 +3,14 @@ using System.Text; namespace Discord { - public class LogMessageEventArgs : EventArgs + public struct LogMessage { public LogSeverity Severity { get; } public string Source { get; } public string Message { get; } public Exception Exception { get; } - public LogMessageEventArgs(LogSeverity severity, string source, string message, Exception exception = null) + public LogMessage(LogSeverity severity, string source, string message, Exception exception = null) { Severity = severity; Source = source; diff --git a/src/Discord.Net/Logging/Logger.cs b/src/Discord.Net/Logging/Logger.cs index 274655948..74435e012 100644 --- a/src/Discord.Net/Logging/Logger.cs +++ b/src/Discord.Net/Logging/Logger.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Discord.Logging { @@ -15,44 +16,44 @@ namespace Discord.Logging Name = name; } - public void Log(LogSeverity severity, string message, Exception exception = null) + public Task Log(LogSeverity severity, string message, Exception exception = null) => _manager.Log(severity, Name, message, exception); - public void Log(LogSeverity severity, FormattableString message, Exception exception = null) + public Task Log(LogSeverity severity, FormattableString message, Exception exception = null) => _manager.Log(severity, Name, message, exception); - public void Error(string message, Exception exception = null) + public Task Error(string message, Exception exception = null) => _manager.Error(Name, message, exception); - public void Error(FormattableString message, Exception exception = null) + public Task Error(FormattableString message, Exception exception = null) => _manager.Error(Name, message, exception); - public void Error(Exception exception) + public Task Error(Exception exception) => _manager.Error(Name, exception); - public void Warning(string message, Exception exception = null) + public Task Warning(string message, Exception exception = null) => _manager.Warning(Name, message, exception); - public void Warning(FormattableString message, Exception exception = null) + public Task Warning(FormattableString message, Exception exception = null) => _manager.Warning(Name, message, exception); - public void Warning(Exception exception) + public Task Warning(Exception exception) => _manager.Warning(Name, exception); - public void Info(string message, Exception exception = null) + public Task Info(string message, Exception exception = null) => _manager.Info(Name, message, exception); - public void Info(FormattableString message, Exception exception = null) + public Task Info(FormattableString message, Exception exception = null) => _manager.Info(Name, message, exception); - public void Info(Exception exception) + public Task Info(Exception exception) => _manager.Info(Name, exception); - public void Verbose(string message, Exception exception = null) + public Task Verbose(string message, Exception exception = null) => _manager.Verbose(Name, message, exception); - public void Verbose(FormattableString message, Exception exception = null) + public Task Verbose(FormattableString message, Exception exception = null) => _manager.Verbose(Name, message, exception); - public void Verbose(Exception exception) + public Task Verbose(Exception exception) => _manager.Verbose(Name, exception); - public void Debug(string message, Exception exception = null) + public Task Debug(string message, Exception exception = null) => _manager.Debug(Name, message, exception); - public void Debug(FormattableString message, Exception exception = null) + public Task Debug(FormattableString message, Exception exception = null) => _manager.Debug(Name, message, exception); - public void Debug(Exception exception) + public Task Debug(Exception exception) => _manager.Debug(Name, exception); } } diff --git a/src/Discord.Net/Net/Queue/RequestQueue.cs b/src/Discord.Net/Net/Queue/RequestQueue.cs index 91ad91711..365ebfb68 100644 --- a/src/Discord.Net/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net/Net/Queue/RequestQueue.cs @@ -12,7 +12,7 @@ namespace Discord.Net.Queue private readonly RequestQueueBucket[] _globalBuckets; private readonly Dictionary[] _guildBuckets; private CancellationTokenSource _clearToken; - private CancellationToken? _parentToken; + private CancellationToken _parentToken; private CancellationToken _cancelToken; public RequestQueue() @@ -20,10 +20,12 @@ namespace Discord.Net.Queue _lock = new SemaphoreSlim(1, 1); _globalBuckets = new RequestQueueBucket[Enum.GetValues(typeof(GlobalBucket)).Length]; _guildBuckets = new Dictionary[Enum.GetValues(typeof(GuildBucket)).Length]; + _clearToken = new CancellationTokenSource(); - _cancelToken = _clearToken.Token; + _cancelToken = CancellationToken.None; + _parentToken = CancellationToken.None; } - internal async Task SetCancelToken(CancellationToken cancelToken) + public async Task SetCancelToken(CancellationToken cancelToken) { await Lock().ConfigureAwait(false); try @@ -33,8 +35,18 @@ namespace Discord.Net.Queue } finally { Unlock(); } } - - internal async Task Send(IQueuedRequest request, BucketGroup group, int bucketId, ulong guildId) + + internal Task Send(RestRequest request, BucketGroup group, int bucketId, ulong guildId) + { + request.CancelToken = _cancelToken; + return Send(request as IQueuedRequest, group, bucketId, guildId); + } + internal Task Send(WebSocketRequest request, BucketGroup group, int bucketId, ulong guildId) + { + request.CancelToken = _cancelToken; + return Send(request as IQueuedRequest, group, bucketId, guildId); + } + private async Task Send(IQueuedRequest request, BucketGroup group, int bucketId, ulong guildId) { RequestQueueBucket bucket; @@ -121,13 +133,13 @@ namespace Discord.Net.Queue return bucket; } - internal void DestroyGlobalBucket(GlobalBucket type) + public void DestroyGlobalBucket(GlobalBucket type) { //Assume this object is locked _globalBuckets[(int)type] = null; } - internal void DestroyGuildBucket(GuildBucket type, ulong guildId) + public void DestroyGuildBucket(GuildBucket type, ulong guildId) { //Assume this object is locked @@ -153,7 +165,7 @@ namespace Discord.Net.Queue _clearToken?.Cancel(); _clearToken = new CancellationTokenSource(); if (_parentToken != null) - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken.Value).Token; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token; else _cancelToken = _clearToken.Token; } diff --git a/src/Discord.Net/Net/Queue/RestRequest.cs b/src/Discord.Net/Net/Queue/RestRequest.cs index 179ce6423..7c71d114a 100644 --- a/src/Discord.Net/Net/Queue/RestRequest.cs +++ b/src/Discord.Net/Net/Queue/RestRequest.cs @@ -15,7 +15,7 @@ namespace Discord.Net.Queue public bool HeaderOnly { get; } public IReadOnlyDictionary MultipartParams { get; } public TaskCompletionSource Promise { get; } - public CancellationToken CancelToken { get; internal set; } + public CancellationToken CancelToken { get; set; } public bool IsMultipart => MultipartParams != null; diff --git a/src/Discord.Net/Net/Queue/WebSocketRequest.cs b/src/Discord.Net/Net/Queue/WebSocketRequest.cs index 47a00ad68..bd8f492c3 100644 --- a/src/Discord.Net/Net/Queue/WebSocketRequest.cs +++ b/src/Discord.Net/Net/Queue/WebSocketRequest.cs @@ -9,25 +9,26 @@ namespace Discord.Net.Queue { public IWebSocketClient Client { get; } public byte[] Data { get; } - public int Offset { get; } - public int Bytes { get; } + public int DataIndex { get; } + public int DataCount { get; } public bool IsText { get; } - public CancellationToken CancelToken { get; } public TaskCompletionSource Promise { get; } + public CancellationToken CancelToken { get; set; } - public WebSocketRequest(byte[] data, bool isText, CancellationToken cancelToken) : this(data, 0, data.Length, isText, cancelToken) { } - public WebSocketRequest(byte[] data, int offset, int length, bool isText, CancellationToken cancelToken) + public WebSocketRequest(IWebSocketClient client, byte[] data, bool isText) : this(client, data, 0, data.Length, isText) { } + public WebSocketRequest(IWebSocketClient client, byte[] data, int index, int count, bool isText) { + Client = client; Data = data; - Offset = offset; - Bytes = length; + DataIndex = index; + DataCount = count; IsText = isText; Promise = new TaskCompletionSource(); } public async Task Send() { - await Client.Send(Data, Offset, Bytes, IsText).ConfigureAwait(false); + await Client.Send(Data, DataIndex, DataCount, IsText).ConfigureAwait(false); return null; } } diff --git a/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs b/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs deleted file mode 100644 index 3fd4425fa..000000000 --- a/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Discord.Net.WebSockets -{ - public class BinaryMessageEventArgs : EventArgs - { - public byte[] Data { get; } - - public BinaryMessageEventArgs(byte[] data) { } - } -} diff --git a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs index e9ae38f55..d9559a2cf 100644 --- a/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs +++ b/src/Discord.Net/Net/WebSockets/DefaultWebsocketClient.cs @@ -14,8 +14,8 @@ namespace Discord.Net.WebSockets public const int SendChunkSize = 4 * 1024; //4KB private const int HR_TIMEOUT = -2147012894; - public event Func BinaryMessage; - public event Func TextMessage; + public event Func BinaryMessage; + public event Func TextMessage; private readonly ClientWebSocket _client; private Task _task; @@ -79,12 +79,12 @@ namespace Discord.Net.WebSockets _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task Send(byte[] data, int offset, int count, bool isText) + public async Task Send(byte[] data, int index, int count, bool isText) { //TODO: If connection is temporarily down, retry? int frameCount = (int)Math.Ceiling((double)count / SendChunkSize); - for (int i = 0; i < frameCount; i++, offset += SendChunkSize) + for (int i = 0; i < frameCount; i++, index += SendChunkSize) { bool isLast = i == (frameCount - 1); @@ -96,7 +96,7 @@ namespace Discord.Net.WebSockets try { - await _client.SendAsync(new ArraySegment(data, offset, count), isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary, isLast, _cancelToken).ConfigureAwait(false); + await _client.SendAsync(new ArraySegment(data, index, count), isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary, isLast, _cancelToken).ConfigureAwait(false); } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) { @@ -139,11 +139,11 @@ namespace Discord.Net.WebSockets var array = stream.ToArray(); if (result.MessageType == WebSocketMessageType.Binary) - await BinaryMessage.Raise(new BinaryMessageEventArgs(array)).ConfigureAwait(false); + await BinaryMessage.Raise(array, 0, array.Length).ConfigureAwait(false); else if (result.MessageType == WebSocketMessageType.Text) { string text = Encoding.UTF8.GetString(array, 0, array.Length); - await TextMessage.Raise(new TextMessageEventArgs(text)).ConfigureAwait(false); + await TextMessage.Raise(text).ConfigureAwait(false); } stream.Position = 0; diff --git a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs index 20cfd0da8..2925c1350 100644 --- a/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs +++ b/src/Discord.Net/Net/WebSockets/IWebSocketClient.cs @@ -4,11 +4,10 @@ using System.Threading.Tasks; namespace Discord.Net.WebSockets { - //TODO: Add ETF public interface IWebSocketClient { - event Func BinaryMessage; - event Func TextMessage; + event Func BinaryMessage; + event Func TextMessage; void SetHeader(string key, string value); void SetCancelToken(CancellationToken cancelToken); @@ -16,6 +15,6 @@ namespace Discord.Net.WebSockets Task Connect(string host); Task Disconnect(); - Task Send(byte[] data, int offset, int length, bool isText); + Task Send(byte[] data, int index, int count, bool isText); } } diff --git a/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs b/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs deleted file mode 100644 index e4e186044..000000000 --- a/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Discord.Net.WebSockets -{ - public class TextMessageEventArgs : EventArgs - { - public string Message { get; } - - public TextMessageEventArgs(string msg) { Message = msg; } - } -} diff --git a/src/Discord.Net/Rest/DiscordClient.cs b/src/Discord.Net/Rest/DiscordClient.cs index 992b52c92..89475ca93 100644 --- a/src/Discord.Net/Rest/DiscordClient.cs +++ b/src/Discord.Net/Rest/DiscordClient.cs @@ -17,7 +17,7 @@ namespace Discord.Rest //TODO: Log Logins/Logouts public sealed class DiscordClient : IDiscordClient, IDisposable { - public event Func Log; + public event Func Log; public event Func LoggedIn, LoggedOut; private readonly Logger _discordLogger, _restLogger; @@ -39,7 +39,7 @@ namespace Discord.Rest config = new DiscordConfig(); _log = new LogManager(config.LogLevel); - _log.Message += async e => await Log.Raise(e).ConfigureAwait(false); + _log.Message += async msg => await Log.Raise(msg).ConfigureAwait(false); _discordLogger = _log.CreateLogger("Discord"); _restLogger = _log.CreateLogger("Rest"); @@ -47,7 +47,7 @@ namespace Discord.Rest _requestQueue = new RequestQueue(); ApiClient = new API.DiscordApiClient(config.RestClientProvider, requestQueue: _requestQueue); - ApiClient.SentRequest += async e => await _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms").ConfigureAwait(false); + ApiClient.SentRequest += async (method, endpoint, millis) => await _log.Verbose("Rest", $"{method} {endpoint}: {millis} ms").ConfigureAwait(false); } public async Task Login(string email, string password) diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 2639c9389..271248def 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -8,6 +8,7 @@ using Discord.Net.Queue; using Discord.Net.WebSockets; using Discord.WebSocket.Data; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -25,7 +26,7 @@ namespace Discord.WebSocket //TODO: Do a final namespace and file structure review public sealed class DiscordClient : IDiscordClient, IDisposable { - public event Func Log; + public event Func Log; public event Func LoggedIn, LoggedOut; public event Func Connected, Disconnected; //public event Func VoiceConnected, VoiceDisconnected; @@ -42,7 +43,7 @@ namespace Discord.WebSocket public event Func UserIsTyping; private readonly ConcurrentQueue _largeGuilds; - private readonly Logger _discordLogger, _gatewayLogger; + private readonly Logger _discordLogger, _restLogger, _gatewayLogger; private readonly SemaphoreSlim _connectionLock; private readonly DataStoreProvider _dataStoreProvider; private readonly LogManager _log; @@ -90,8 +91,9 @@ namespace Discord.WebSocket _largeThreshold = config.LargeThreshold; _log = new LogManager(config.LogLevel); - _log.Message += async e => await Log.Raise(e).ConfigureAwait(false); + _log.Message += async msg => await Log.Raise(msg).ConfigureAwait(false); _discordLogger = _log.CreateLogger("Discord"); + _restLogger = _log.CreateLogger("Rest"); _gatewayLogger = _log.CreateLogger("Gateway"); _connectionLock = new SemaphoreSlim(1, 1); @@ -99,21 +101,10 @@ namespace Discord.WebSocket _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; ApiClient = new API.DiscordApiClient(config.RestClientProvider, config.WebSocketProvider, _serializer, _requestQueue); - ApiClient.SentRequest += async e => await _log.Verbose("Rest", $"{e.Method} {e.Endpoint}: {e.Milliseconds} ms"); + ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.Verbose($"{method} {endpoint}: {millis} ms"); + ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.Verbose($"Sent Op {opCode}"); + ApiClient.ReceivedGatewayEvent += ProcessMessage; GatewaySocket = config.WebSocketProvider(); - GatewaySocket.BinaryMessage += async e => - { - using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2)) - using (var decompressed = new MemoryStream()) - { - using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - zlib.CopyTo(decompressed); - decompressed.Position = 0; - using (var reader = new StreamReader(decompressed)) - await ProcessMessage(reader.ReadToEnd()).ConfigureAwait(false); - } - }; - GatewaySocket.TextMessage += async e => await ProcessMessage(e.Message).ConfigureAwait(false); _voiceRegions = ImmutableDictionary.Create(); _largeGuilds = new ConcurrentQueue(); @@ -169,11 +160,8 @@ namespace Discord.WebSocket try { await ApiClient.ValidateToken().ConfigureAwait(false); - var gateway = await ApiClient.GetGateway(); var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); - - await GatewaySocket.Connect(gateway.Url).ConfigureAwait(false); } catch (HttpException ex) { @@ -325,543 +313,548 @@ namespace Discord.WebSocket return null; } - private async Task ProcessMessage(string json) + private async Task ProcessMessage(GatewayOpCodes opCode, string type, JToken payload) { - var msg = JsonConvert.DeserializeObject(json); try { - switch (msg.Type) + switch (opCode) { - //Global - case "READY": + case GatewayOpCodes.Dispatch: + switch (type) { - //TODO: Store guilds even if they're unavailable - //TODO: Make downloading large guilds optional - - var data = msg.Payload.ToObject(_serializer); - var store = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); - - _sessionId = data.SessionId; - var currentUser = new SelfUser(this, data.User); - store.AddUser(currentUser); + //Global + case "READY": + { + //TODO: Store guilds even if they're unavailable + //TODO: Make downloading large guilds optional + //TODO: Add support for unavailable guilds - for (int i = 0; i < data.Guilds.Length; i++) - { - var model = data.Guilds[i]; - var guild = new Guild(this, model); - store.AddGuild(guild); + var data = payload.ToObject(_serializer); + var store = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length); - foreach (var channel in guild.Channels) - store.AddChannel(channel); + _sessionId = data.SessionId; + var currentUser = new SelfUser(this, data.User); + store.AddUser(currentUser); - /*if (model.IsLarge) - _largeGuilds.Enqueue(model.Id);*/ - } + for (int i = 0; i < data.Guilds.Length; i++) + { + var model = data.Guilds[i]; + var guild = new Guild(this, model); + store.AddGuild(guild); - for (int i = 0; i < data.PrivateChannels.Length; i++) - { - var model = data.PrivateChannels[i]; - var recipient = new User(this, model.Recipient); - var channel = new DMChannel(this, recipient, model); + foreach (var channel in guild.Channels) + store.AddChannel(channel); - recipient.DMChannel = channel; - store.AddChannel(channel); - } + /*if (model.IsLarge) + _largeGuilds.Enqueue(model.Id);*/ + } - CurrentUser = currentUser; - DataStore = store; - } - break; + for (int i = 0; i < data.PrivateChannels.Length; i++) + { + var model = data.PrivateChannels[i]; + var recipient = new User(this, model.Recipient); + var channel = new DMChannel(this, recipient, model); - //Servers - case "GUILD_CREATE": - { - /*var data = msg.Payload.ToObject(Serializer); - if (data.Unavailable != true) - { - var server = AddServer(data.Id); - server.Update(data); + recipient.DMChannel = channel; + store.AddChannel(channel); + } - if (data.Unavailable != false) - { - _gatewayLogger.Info($"GUILD_CREATE: {server.Path}"); - JoinedServer.Raise(server); + CurrentUser = currentUser; + DataStore = store; } - else - _gatewayLogger.Info($"GUILD_AVAILABLE: {server.Path}"); - - if (!data.IsLarge) - await GuildAvailable.Raise(server); - else - _largeServers.Enqueue(data.Id); - }*/ - } - break; - case "GUILD_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.Id); - if (server != null) - { - var before = Config.EnablePreUpdateEvents ? server.Clone() : null; - server.Update(data); - _gatewayLogger.Info($"GUILD_UPDATE: {server.Path}"); - await GuildUpdated.Raise(before, server); - } - else - _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");*/ - } - break; - case "GUILD_DELETE": - { - /*var data = msg.Payload.ToObject(Serializer); - Server server = RemoveServer(data.Id); - if (server != null) - { - if (data.Unavailable != true) - _gatewayLogger.Info($"GUILD_DELETE: {server.Path}"); - else - _gatewayLogger.Info($"GUILD_UNAVAILABLE: {server.Path}"); - - OnServerUnavailable(server); - if (data.Unavailable != true) - OnLeftServer(server); - } - else - _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild.");*/ - } - break; - - //Channels - case "CHANNEL_CREATE": - { - /*var data = msg.Payload.ToObject(Serializer); - - Channel channel = null; - if (data.GuildId != null) - { - var server = GetServer(data.GuildId.Value); - if (server != null) - channel = server.AddChannel(data.Id, true); - else - _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild."); - } - else - channel = AddPrivateChannel(data.Id, data.Recipient.Id); - if (channel != null) - { - channel.Update(data); - _gatewayLogger.Info($"CHANNEL_CREATE: {channel.Path}"); - ChannelCreated.Raise(new ChannelEventArgs(channel)); - }*/ - } - break; - case "CHANNEL_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var channel = GetChannel(data.Id); - if (channel != null) - { - var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; - channel.Update(data); - _gateway_gatewayLogger.Info($"CHANNEL_UPDATE: {channel.Path}"); - OnChannelUpdated(before, channel); - } - else - _gateway_gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");*/ - } - break; - case "CHANNEL_DELETE": - { - /*var data = msg.Payload.ToObject(Serializer); - var channel = RemoveChannel(data.Id); - if (channel != null) - { - _gateway_gatewayLogger.Info($"CHANNEL_DELETE: {channel.Path}"); - OnChannelDestroyed(channel); - } - else - _gateway_gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");*/ - } - break; + break; - //Members - case "GUILD_MEMBER_ADD": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.AddUser(data.User.Id, true, true); - user.Update(data); - user.UpdateActivity(); - _gatewayLogger.Info($"GUILD_MEMBER_ADD: {user.Path}"); - OnUserJoined(user); - } - else - _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");*/ - } - break; - case "GUILD_MEMBER_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.GetUser(data.User.Id); - if (user != null) + //Servers + case "GUILD_CREATE": { - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - _gatewayLogger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); - OnUserUpdated(before, user); + /*var data = msg.Payload.ToObject(Serializer); + if (data.Unavailable != true) + { + var server = AddServer(data.Id); + server.Update(data); + + if (data.Unavailable != false) + { + _gatewayLogger.Info($"GUILD_CREATE: {server.Path}"); + JoinedServer.Raise(server); + } + else + _gatewayLogger.Info($"GUILD_AVAILABLE: {server.Path}"); + + if (!data.IsLarge) + await GuildAvailable.Raise(server); + else + _largeServers.Enqueue(data.Id); + }*/ } - else - _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); - } - else - _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");*/ - } - break; - case "GUILD_MEMBER_REMOVE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.RemoveUser(data.User.Id); - if (user != null) + break; + case "GUILD_UPDATE": { - _gatewayLogger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); - OnUserLeft(user); + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.Id); + if (server != null) + { + var before = Config.EnablePreUpdateEvents ? server.Clone() : null; + server.Update(data); + _gatewayLogger.Info($"GUILD_UPDATE: {server.Path}"); + await GuildUpdated.Raise(before, server); + } + else + _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");*/ } - else - _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); - } - else - _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");*/ - } - break; - case "GUILD_MEMBERS_CHUNK": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - foreach (var memberData in data.Members) + break; + case "GUILD_DELETE": { - var user = server.AddUser(memberData.User.Id, true, false); - user.Update(memberData); + /*var data = msg.Payload.ToObject(Serializer); + Server server = RemoveServer(data.Id); + if (server != null) + { + if (data.Unavailable != true) + _gatewayLogger.Info($"GUILD_DELETE: {server.Path}"); + else + _gatewayLogger.Info($"GUILD_UNAVAILABLE: {server.Path}"); + + OnServerUnavailable(server); + if (data.Unavailable != true) + OnLeftServer(server); + } + else + _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild.");*/ } - _gateway_gatewayLogger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); + break; - if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there - OnServerAvailable(server); - } - else - _gateway_gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");*/ - } - break; + //Channels + case "CHANNEL_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); - //Roles - case "GUILD_ROLE_CREATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.AddRole(data.Data.Id); - role.Update(data.Data, false); - _gateway_gatewayLogger.Info($"GUILD_ROLE_CREATE: {role.Path}"); - OnRoleCreated(role); - } - else - _gateway_gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");*/ - } - break; - case "GUILD_ROLE_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.GetRole(data.Data.Id); - if (role != null) + Channel channel = null; + if (data.GuildId != null) + { + var server = GetServer(data.GuildId.Value); + if (server != null) + channel = server.AddChannel(data.Id, true); + else + _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild."); + } + else + channel = AddPrivateChannel(data.Id, data.Recipient.Id); + if (channel != null) + { + channel.Update(data); + _gatewayLogger.Info($"CHANNEL_CREATE: {channel.Path}"); + ChannelCreated.Raise(channel); + }*/ + } + break; + case "CHANNEL_UPDATE": { - var before = Config.EnablePreUpdateEvents ? role.Clone() : null; - role.Update(data.Data, true); - _gateway_gatewayLogger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); - OnRoleUpdated(before, role); + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.Id); + if (channel != null) + { + var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; + channel.Update(data); + _gateway_gatewayLogger.Info($"CHANNEL_UPDATE: {channel.Path}"); + OnChannelUpdated(before, channel); + } + else + _gateway_gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");*/ } - else - _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); - } - else - _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");*/ - } - break; - case "GUILD_ROLE_DELETE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var role = server.RemoveRole(data.RoleId); - if (role != null) + break; + case "CHANNEL_DELETE": { - _gateway_gatewayLogger.Info($"GUILD_ROLE_DELETE: {role.Path}"); - OnRoleDeleted(role); + /*var data = msg.Payload.ToObject(Serializer); + var channel = RemoveChannel(data.Id); + if (channel != null) + { + _gateway_gatewayLogger.Info($"CHANNEL_DELETE: {channel.Path}"); + OnChannelDestroyed(channel); + } + else + _gateway_gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");*/ } - else - _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); - } - else - _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");*/ - } - break; + break; - //Bans - case "GUILD_BAN_ADD": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = server.GetUser(data.User.Id); - if (user != null) + //Members + case "GUILD_MEMBER_ADD": { - _gateway_gatewayLogger.Info($"GUILD_BAN_ADD: {user.Path}"); - OnUserBanned(user); + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.AddUser(data.User.Id, true, true); + user.Update(data); + user.UpdateActivity(); + _gatewayLogger.Info($"GUILD_MEMBER_ADD: {user.Path}"); + OnUserJoined(user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");*/ } - else - _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown user."); - } - else - _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");*/ - } - break; - case "GUILD_BAN_REMOVE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId.Value); - if (server != null) - { - var user = new User(this, data.User.Id, server); - user.Update(data.User); - _gateway_gatewayLogger.Info($"GUILD_BAN_REMOVE: {user.Path}"); - OnUserUnbanned(user); - } - else - _gateway_gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");*/ - } - break; - - //Messages - case "MESSAGE_CREATE": - { - /*var data = msg.Payload.ToObject(Serializer); - - Channel channel = GetChannel(data.ChannelId); - if (channel != null) - { - var user = channel.GetUserFast(data.Author.Id); + break; + case "GUILD_MEMBER_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + _gatewayLogger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); + OnUserUpdated(before, user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");*/ + } + break; + case "GUILD_MEMBER_REMOVE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.RemoveUser(data.User.Id); + if (user != null) + { + _gatewayLogger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); + OnUserLeft(user); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); + } + else + _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");*/ + } + break; + case "GUILD_MEMBERS_CHUNK": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + foreach (var memberData in data.Members) + { + var user = server.AddUser(memberData.User.Id, true, false); + user.Update(memberData); + } + _gateway_gatewayLogger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); + + if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there + OnServerAvailable(server); + } + else + _gateway_gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");*/ + } + break; - if (user != null) + //Roles + case "GUILD_ROLE_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.AddRole(data.Data.Id); + role.Update(data.Data, false); + _gateway_gatewayLogger.Info($"GUILD_ROLE_CREATE: {role.Path}"); + OnRoleCreated(role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");*/ + } + break; + case "GUILD_ROLE_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.GetRole(data.Data.Id); + if (role != null) + { + var before = Config.EnablePreUpdateEvents ? role.Clone() : null; + role.Update(data.Data, true); + _gateway_gatewayLogger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); + OnRoleUpdated(before, role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");*/ + } + break; + case "GUILD_ROLE_DELETE": { - Message msg = null; - bool isAuthor = data.Author.Id == CurrentUser.Id; - //ulong nonce = 0; - - //if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) - //{ - // if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) - // msg = _messages[nonce]; - //} - if (msg == null) + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) { - msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); - //nonce = 0; + var role = server.RemoveRole(data.RoleId); + if (role != null) + { + _gateway_gatewayLogger.Info($"GUILD_ROLE_DELETE: {role.Path}"); + OnRoleDeleted(role); + } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); } + else + _gateway_gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");*/ + } + break; - //Remapped queued message - //if (nonce != 0) - //{ - // msg = _messages.Remap(nonce, data.Id); - // msg.Id = data.Id; - // RaiseMessageSent(msg); - //} + //Bans + case "GUILD_BAN_ADD": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + _gateway_gatewayLogger.Info($"GUILD_BAN_ADD: {user.Path}"); + OnUserBanned(user); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown user."); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");*/ + } + break; + case "GUILD_BAN_REMOVE": + { + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = new User(this, data.User.Id, server); + user.Update(data.User); + _gateway_gatewayLogger.Info($"GUILD_BAN_REMOVE: {user.Path}"); + OnUserUnbanned(user); + } + else + _gateway_gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");*/ + } + break; - msg.Update(data); - user.UpdateActivity(); + //Messages + case "MESSAGE_CREATE": + { + /*var data = msg.Payload.ToObject(Serializer); - _gateway_gatewayLogger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); - OnMessageReceived(msg); + Channel channel = GetChannel(data.ChannelId); + if (channel != null) + { + var user = channel.GetUserFast(data.Author.Id); + + if (user != null) + { + Message msg = null; + bool isAuthor = data.Author.Id == CurrentUser.Id; + //ulong nonce = 0; + + //if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) + //{ + // if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) + // msg = _messages[nonce]; + //} + if (msg == null) + { + msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); + //nonce = 0; + } + + //Remapped queued message + //if (nonce != 0) + //{ + // msg = _messages.Remap(nonce, data.Id); + // msg.Id = data.Id; + // RaiseMessageSent(msg); + //} + + msg.Update(data); + user.UpdateActivity(); + + _gateway_gatewayLogger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); + OnMessageReceived(msg); + } + else + _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user."); + } + else + _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");*/ } - else - _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user."); - } - else - _gateway_gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");*/ - } - break; - case "MESSAGE_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - var msg = channel.GetMessage(data.Id, data.Author?.Id); - var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; - msg.Update(data); - _gatewayLogger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); - OnMessageUpdated(before, msg); - } - else - _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");*/ - } - break; - case "MESSAGE_DELETE": - { - /*var data = msg.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - var msg = channel.RemoveMessage(data.Id); - _gatewayLogger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); - OnMessageDeleted(msg); - } - else - _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");*/ - } - break; - - //Statuses - case "PRESENCE_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - User user; - Server server; - if (data.GuildId == null) - { - server = null; - user = GetPrivateChannel(data.User.Id)?.Recipient; - } - else - { - server = GetServer(data.GuildId.Value); - if (server == null) + break; + case "MESSAGE_UPDATE": { - _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown server."); - break; + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.GetMessage(data.Id, data.Author?.Id); + var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; + msg.Update(data); + _gatewayLogger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); + OnMessageUpdated(before, msg); + } + else + _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");*/ } - else - user = server.GetUser(data.User.Id); - } - - if (user != null) - { - if (Config.LogLevel == LogSeverity.Debug) - _gatewayLogger.Debug($"PRESENCE_UPDATE: {user.Path}"); - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - OnUserUpdated(before, user); - } - //else //Occurs when a user leaves a server - // _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ - } - break; - case "TYPING_START": - { - /*var data = msg.Payload.ToObject(Serializer); - var channel = GetChannel(data.ChannelId); - if (channel != null) - { - User user; - if (channel.IsPrivate) + break; + case "MESSAGE_DELETE": { - if (channel.Recipient.Id == data.UserId) - user = channel.Recipient; + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.RemoveMessage(data.Id); + _gatewayLogger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); + OnMessageDeleted(msg); + } else - break; + _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");*/ } - else - user = channel.Server.GetUser(data.UserId); - if (user != null) + break; + + //Statuses + case "PRESENCE_UPDATE": { - if (Config.LogLevel == LogSeverity.Debug) - _gatewayLogger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); - OnUserIsTypingUpdated(channel, user); - user.UpdateActivity(); + /*var data = msg.Payload.ToObject(Serializer); + User user; + Server server; + if (data.GuildId == null) + { + server = null; + user = GetPrivateChannel(data.User.Id)?.Recipient; + } + else + { + server = GetServer(data.GuildId.Value); + if (server == null) + { + _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown server."); + break; + } + else + user = server.GetUser(data.User.Id); + } + + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"PRESENCE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + OnUserUpdated(before, user); + } + //else //Occurs when a user leaves a server + // _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ } - } - else - _gatewayLogger.Warning("TYPING_START referenced an unknown channel.");*/ - } - break; + break; + case "TYPING_START": + { + /*var data = msg.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + User user; + if (channel.IsPrivate) + { + if (channel.Recipient.Id == data.UserId) + user = channel.Recipient; + else + break; + } + else + user = channel.Server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); + OnUserIsTypingUpdated(channel, user); + user.UpdateActivity(); + } + } + else + _gatewayLogger.Warning("TYPING_START referenced an unknown channel.");*/ + } + break; - //Voice - case "VOICE_STATE_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - var server = GetServer(data.GuildId); - if (server != null) - { - var user = server.GetUser(data.UserId); - if (user != null) + //Voice + case "VOICE_STATE_UPDATE": { - if (Config.LogLevel == LogSeverity.Debug) - _gatewayLogger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); - var before = Config.EnablePreUpdateEvents ? user.Clone() : null; - user.Update(data); - //_gatewayLogger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); - OnUserUpdated(before, user); + /*var data = msg.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var user = server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + _gatewayLogger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + //_gatewayLogger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); + OnUserUpdated(before, user); + } + //else //Occurs when a user leaves a server + // _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user."); + } + else + _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown server.");*/ } - //else //Occurs when a user leaves a server - // _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user."); - } - else - _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown server.");*/ - } - break; + break; - //Settings - case "USER_UPDATE": - { - /*var data = msg.Payload.ToObject(Serializer); - if (data.Id == CurrentUser.Id) - { - var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; - CurrentUser.Update(data); - foreach (var server in _servers) - server.Value.CurrentUser.Update(data); - _gatewayLogger.Info($"USER_UPDATE"); - OnProfileUpdated(before, CurrentUser); - }*/ + //Settings + case "USER_UPDATE": + { + /*var data = msg.Payload.ToObject(Serializer); + if (data.Id == CurrentUser.Id) + { + var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; + CurrentUser.Update(data); + foreach (var server in _servers) + server.Value.CurrentUser.Update(data); + _gatewayLogger.Info($"USER_UPDATE"); + OnProfileUpdated(before, CurrentUser); + }*/ + } + break; + + //Handled in GatewaySocket + case "RESUMED": + break; + + //Ignored + case "USER_SETTINGS_UPDATE": + case "MESSAGE_ACK": //TODO: Add (User only) + case "GUILD_EMOJIS_UPDATE": //TODO: Add + case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add + case "VOICE_SERVER_UPDATE": //TODO: Add + _gatewayLogger.Debug($"Ignored message {opCode}{(type != null ? $" ({type})" : "")}"); + break; + + //Others + default: + _gatewayLogger.Warning($"Unknown message {opCode}{(type != null ? $" ({type})" : "")}"); + break; } break; - - //Handled in GatewaySocket - case "RESUMED": - break; - - //Ignored - case "USER_SETTINGS_UPDATE": - case "MESSAGE_ACK": //TODO: Add (User only) - case "GUILD_EMOJIS_UPDATE": //TODO: Add - case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add - case "VOICE_SERVER_UPDATE": //TODO: Add - _gatewayLogger.Debug($"{msg.Type} [Ignored]"); - break; - - //Others - default: - _gatewayLogger.Warning($"Unknown message type: {msg.Type}"); - break; } } catch (Exception ex) { - _gatewayLogger.Error($"Error handling {msg.Type} event", ex); + _gatewayLogger.Error($"Error handling msg {opCode}{(type != null ? $" ({type})" : "")}", ex); } } diff --git a/src/Discord.Net/WebSocket/Entities/Users/User.cs b/src/Discord.Net/WebSocket/Entities/Users/User.cs index a69f3e5f4..e507b4df8 100644 --- a/src/Discord.Net/WebSocket/Entities/Users/User.cs +++ b/src/Discord.Net/WebSocket/Entities/Users/User.cs @@ -24,6 +24,10 @@ namespace Discord.WebSocket public string Username { get; private set; } /// public DMChannel DMChannel { get; internal set; } + /// + public Game? CurrentGame { get; internal set; } + /// + public UserStatus Status { get; internal set; } /// public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId); @@ -65,11 +69,6 @@ namespace Discord.WebSocket public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; - /// - Game? IUser.CurrentGame => null; - /// - UserStatus IUser.Status => UserStatus.Unknown; - /// async Task IUser.CreateDMChannel() => await CreateDMChannel().ConfigureAwait(false); From e16eb4e895f12f4ac2df7696a95f5991c018bd0e Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 05:32:15 -0300 Subject: [PATCH 36/39] Fixed voice region caching --- src/Discord.Net/WebSocket/DiscordClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/WebSocket/DiscordClient.cs b/src/Discord.Net/WebSocket/DiscordClient.cs index 271248def..911420731 100644 --- a/src/Discord.Net/WebSocket/DiscordClient.cs +++ b/src/Discord.Net/WebSocket/DiscordClient.cs @@ -160,8 +160,6 @@ namespace Discord.WebSocket try { await ApiClient.ValidateToken().ConfigureAwait(false); - var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); - _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); } catch (HttpException ex) { @@ -169,6 +167,9 @@ namespace Discord.WebSocket } } + var voiceRegions = await ApiClient.GetVoiceRegions().ConfigureAwait(false); + _voiceRegions = voiceRegions.Select(x => new VoiceRegion(x)).ToImmutableDictionary(x => x.Id); + LoginState = LoginState.LoggedIn; } catch (Exception) From 89fadb52f6a5fc7f20e71e9b5c4c6668e6ba9efc Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 10:48:52 -0300 Subject: [PATCH 37/39] Moved Roles out of the primary data store --- src/Discord.Net/WebSocket/Data/DefaultDataStore.cs | 3 --- src/Discord.Net/WebSocket/Data/IDataStore.cs | 5 ----- 2 files changed, 8 deletions(-) diff --git a/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs b/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs index 1308792a6..28a4ca0d1 100644 --- a/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs +++ b/src/Discord.Net/WebSocket/Data/DefaultDataStore.cs @@ -1,14 +1,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; namespace Discord.WebSocket.Data { public class DefaultDataStore : IDataStore { private const double AverageChannelsPerGuild = 10.22; //Source: Googie2149 - private const double AverageRolesPerGuild = 5; //Source: Googie2149 //TODO: Get a real value private const double AverageUsersPerGuild = 47.78; //Source: Googie2149 private const double CollectionMultiplier = 1.05; //Add buffer to handle growth private const double CollectionConcurrencyLevel = 1; //WebSocket updater/event handler. //TODO: Needs profiling, increase to 2? @@ -27,7 +25,6 @@ namespace Discord.WebSocket.Data { _channels = new ConcurrentDictionary(1, (int)((guildCount * AverageChannelsPerGuild + dmChannelCount) * CollectionMultiplier)); _guilds = new ConcurrentDictionary(1, (int)(guildCount * CollectionMultiplier)); - _roles = new ConcurrentDictionary(1, (int)(guildCount * AverageRolesPerGuild * CollectionMultiplier)); _users = new ConcurrentDictionary(1, (int)(guildCount * AverageUsersPerGuild * CollectionMultiplier)); } diff --git a/src/Discord.Net/WebSocket/Data/IDataStore.cs b/src/Discord.Net/WebSocket/Data/IDataStore.cs index 1a9d6e450..b980d13d5 100644 --- a/src/Discord.Net/WebSocket/Data/IDataStore.cs +++ b/src/Discord.Net/WebSocket/Data/IDataStore.cs @@ -6,7 +6,6 @@ namespace Discord.WebSocket.Data { IEnumerable Channels { get; } IEnumerable Guilds { get; } - IEnumerable Roles { get; } IEnumerable Users { get; } Channel GetChannel(ulong id); @@ -17,10 +16,6 @@ namespace Discord.WebSocket.Data void AddGuild(Guild guild); Guild RemoveGuild(ulong id); - Role GetRole(ulong id); - void AddRole(Role role); - Role RemoveRole(ulong id); - User GetUser(ulong id); void AddUser(User user); User RemoveUser(ulong id); From 48dc622fb3059146a8f24f0aa2dbd84417aba9d0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 10:49:01 -0300 Subject: [PATCH 38/39] Added ConcurrentHashSet --- src/Discord.Net/ConcurrentHashSet.cs | 475 +++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 src/Discord.Net/ConcurrentHashSet.cs diff --git a/src/Discord.Net/ConcurrentHashSet.cs b/src/Discord.Net/ConcurrentHashSet.cs new file mode 100644 index 000000000..18572cfcf --- /dev/null +++ b/src/Discord.Net/ConcurrentHashSet.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Threading; + +namespace Discord +{ + //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs + //Copyright (c) .NET Foundation and Contributors + [DebuggerDisplay("Count = {Count}")] + internal class ConcurrentHashSet : IEnumerable + { + private sealed class Tables + { + internal readonly Node[] _buckets; + internal readonly object[] _locks; + internal volatile int[] _countPerLock; + + internal Tables(Node[] buckets, object[] locks, int[] countPerLock) + { + _buckets = buckets; + _locks = locks; + _countPerLock = countPerLock; + } + } + private sealed class Node + { + internal readonly T _value; + internal volatile Node _next; + internal readonly int _hashcode; + + internal Node(T key, int hashcode, Node next) + { + _value = key; + _next = next; + _hashcode = hashcode; + } + } + + private const int DefaultCapacity = 31; + private const int MaxLockNumber = 1024; + + private static int GetBucket(int hashcode, int bucketCount) + { + int bucketNo = (hashcode & 0x7fffffff) % bucketCount; + return bucketNo; + } + private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; + } + private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; + + private volatile Tables _tables; + private readonly IEqualityComparer _comparer; + private readonly bool _growLockArray; + private int _budget; + + public int Count + { + get + { + int count = 0; + + int acquiredLocks = 0; + try + { + AcquireAllLocks(ref acquiredLocks); + + for (int i = 0; i < _tables._countPerLock.Length; i++) + count += _tables._countPerLock[i]; + } + finally { ReleaseLocks(0, acquiredLocks); } + + return count; + } + } + public bool IsEmpty + { + get + { + int acquiredLocks = 0; + try + { + // Acquire all locks + AcquireAllLocks(ref acquiredLocks); + + for (int i = 0; i < _tables._countPerLock.Length; i++) + { + if (_tables._countPerLock[i] != 0) + return false; + } + } + finally { ReleaseLocks(0, acquiredLocks); } + + return true; + } + } + public ReadOnlyCollection Values + { + get + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + List values = new List(); + + for (int i = 0; i < _tables._buckets.Length; i++) + { + Node current = _tables._buckets[i]; + while (current != null) + { + values.Add(current._value); + current = current._next; + } + } + + return new ReadOnlyCollection(values); + } + finally { ReleaseLocks(0, locksAcquired); } + } + } + + public ConcurrentHashSet() + : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer.Default) { } + public ConcurrentHashSet(int concurrencyLevel, int capacity) + : this(concurrencyLevel, capacity, false, EqualityComparer.Default) { } + public ConcurrentHashSet(IEnumerable collection) + : this(collection, EqualityComparer.Default) { } + public ConcurrentHashSet(IEqualityComparer comparer) + : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } + public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) + : this(comparer) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); + InitializeFromCollection(collection); + } + public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) + : this(concurrencyLevel, DefaultCapacity, false, comparer) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + InitializeFromCollection(collection); + } + public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) + : this(concurrencyLevel, capacity, false, comparer) { } + internal ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) + { + if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + + if (capacity < concurrencyLevel) + capacity = concurrencyLevel; + + object[] locks = new object[concurrencyLevel]; + for (int i = 0; i < locks.Length; i++) + locks[i] = new object(); + + int[] countPerLock = new int[locks.Length]; + Node[] buckets = new Node[capacity]; + _tables = new Tables(buckets, locks, countPerLock); + + _comparer = comparer; + _growLockArray = growLockArray; + _budget = buckets.Length / locks.Length; + } + private void InitializeFromCollection(IEnumerable collection) + { + foreach (var value in collection) + { + if (value == null) throw new ArgumentNullException("key"); + + if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) + throw new ArgumentException(); + } + + if (_budget == 0) + _budget = _tables._buckets.Length / _tables._locks.Length; + } + + public bool ContainsKey(T value) + { + if (value == null) throw new ArgumentNullException("key"); + return ContainsKeyInternal(value, _comparer.GetHashCode(value)); + } + private bool ContainsKeyInternal(T value, int hashcode) + { + Tables tables = _tables; + + int bucketNo = GetBucket(hashcode, tables._buckets.Length); + + Node n = Volatile.Read(ref tables._buckets[bucketNo]); + + while (n != null) + { + if (hashcode == n._hashcode && _comparer.Equals(n._value, value)) + return true; + n = n._next; + } + + return false; + } + + public bool TryAdd(T value) + { + if (value == null) throw new ArgumentNullException("key"); + return TryAddInternal(value, _comparer.GetHashCode(value), true); + } + private bool TryAddInternal(T value, int hashcode, bool acquireLock) + { + while (true) + { + int bucketNo, lockNo; + + Tables tables = _tables; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + bool resizeDesired = false; + bool lockTaken = false; + try + { + if (acquireLock) + Monitor.Enter(tables._locks[lockNo], ref lockTaken); + + if (tables != _tables) + continue; + + Node prev = null; + for (Node node = tables._buckets[bucketNo]; node != null; node = node._next) + { + if (hashcode == node._hashcode && _comparer.Equals(node._value, value)) + return false; + prev = node; + } + + Volatile.Write(ref tables._buckets[bucketNo], new Node(value, hashcode, tables._buckets[bucketNo])); + checked { tables._countPerLock[lockNo]++; } + + if (tables._countPerLock[lockNo] > _budget) + resizeDesired = true; + } + finally + { + if (lockTaken) + Monitor.Exit(tables._locks[lockNo]); + } + + if (resizeDesired) + GrowTable(tables); + + return true; + } + } + + public bool TryRemove(T value) + { + if (value == null) throw new ArgumentNullException("key"); + return TryRemoveInternal(value); + } + private bool TryRemoveInternal(T value) + { + int hashcode = _comparer.GetHashCode(value); + while (true) + { + Tables tables = _tables; + + int bucketNo, lockNo; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables._buckets.Length, tables._locks.Length); + + lock (tables._locks[lockNo]) + { + if (tables != _tables) + continue; + + Node prev = null; + for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr._next) + { + if (hashcode == curr._hashcode && _comparer.Equals(curr._value, value)) + { + if (prev == null) + Volatile.Write(ref tables._buckets[bucketNo], curr._next); + else + prev._next = curr._next; + + value = curr._value; + tables._countPerLock[lockNo]--; + return true; + } + prev = curr; + } + } + + value = default(T); + return false; + } + } + + public void Clear() + { + int locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + Tables newTables = new Tables(new Node[DefaultCapacity], _tables._locks, new int[_tables._countPerLock.Length]); + _tables = newTables; + _budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + public IEnumerator GetEnumerator() + { + Node[] buckets = _tables._buckets; + + for (int i = 0; i < buckets.Length; i++) + { + Node current = Volatile.Read(ref buckets[i]); + + while (current != null) + { + yield return current._value; + current = current._next; + } + } + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private void GrowTable(Tables tables) + { + const int MaxArrayLength = 0X7FEFFFFF; + int locksAcquired = 0; + try + { + AcquireLocks(0, 1, ref locksAcquired); + if (tables != _tables) + return; + + long approxCount = 0; + for (int i = 0; i < tables._countPerLock.Length; i++) + approxCount += tables._countPerLock[i]; + + if (approxCount < tables._buckets.Length / 4) + { + _budget = 2 * _budget; + if (_budget < 0) + _budget = int.MaxValue; + return; + } + + int newLength = 0; + bool maximizeTableSize = false; + try + { + checked + { + newLength = tables._buckets.Length * 2 + 1; + while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) + newLength += 2; + + if (newLength > MaxArrayLength) + maximizeTableSize = true; + } + } + catch (OverflowException) + { + maximizeTableSize = true; + } + + if (maximizeTableSize) + { + newLength = MaxArrayLength; + _budget = int.MaxValue; + } + + AcquireLocks(1, tables._locks.Length, ref locksAcquired); + + object[] newLocks = tables._locks; + + if (_growLockArray && tables._locks.Length < MaxLockNumber) + { + newLocks = new object[tables._locks.Length * 2]; + Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length); + for (int i = tables._locks.Length; i < newLocks.Length; i++) + newLocks[i] = new object(); + } + + Node[] newBuckets = new Node[newLength]; + int[] newCountPerLock = new int[newLocks.Length]; + + for (int i = 0; i < tables._buckets.Length; i++) + { + Node current = tables._buckets[i]; + while (current != null) + { + Node next = current._next; + int newBucketNo, newLockNo; + GetBucketAndLockNo(current._hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); + + newBuckets[newBucketNo] = new Node(current._value, current._hashcode, newBuckets[newBucketNo]); + + checked { newCountPerLock[newLockNo]++; } + + current = next; + } + } + + _budget = Math.Max(1, newBuckets.Length / newLocks.Length); + _tables = new Tables(newBuckets, newLocks, newCountPerLock); + } + finally { ReleaseLocks(0, locksAcquired); } + } + + private void AcquireAllLocks(ref int locksAcquired) + { + AcquireLocks(0, 1, ref locksAcquired); + AcquireLocks(1, _tables._locks.Length, ref locksAcquired); + } + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) + { + object[] locks = _tables._locks; + + for (int i = fromInclusive; i < toExclusive; i++) + { + bool lockTaken = false; + try + { + Monitor.Enter(locks[i], ref lockTaken); + } + finally + { + if (lockTaken) + locksAcquired++; + } + } + } + private void ReleaseLocks(int fromInclusive, int toExclusive) + { + for (int i = fromInclusive; i < toExclusive; i++) + Monitor.Exit(_tables._locks[i]); + } + } + + //https://github.com/dotnet/corefx/blob/d0dc5fc099946adc1035b34a8b1f6042eddb0c75/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs + //Copyright (c) .NET Foundation and Contributors + internal static class PlatformHelper + { + private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; + private static volatile int s_processorCount; + private static volatile int s_lastProcessorCountRefreshTicks; + + internal static int ProcessorCount + { + get + { + int now = Environment.TickCount; + if (s_processorCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) + { + s_processorCount = Environment.ProcessorCount; + s_lastProcessorCountRefreshTicks = now; + } + + return s_processorCount; + } + } + } +} \ No newline at end of file From 597cedae6d4dd100c9cb1d5f41afa7521dda99fc Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 27 May 2016 10:49:45 -0300 Subject: [PATCH 39/39] Redirected Guild.Channels to data store --- src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs index 19657d9bb..c0663e924 100644 --- a/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/WebSocket/Entities/Guilds/Guild.cs @@ -14,7 +14,7 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class Guild : IGuild, IUserGuild { - private ConcurrentDictionary _channels; + private ConcurrentHashSet _channels; private ConcurrentDictionary _members; private ConcurrentDictionary _roles; private ulong _ownerId; @@ -71,11 +71,11 @@ namespace Discord.WebSocket /// Gets the embed channel for this guild. public IChannel EmbedChannel => GetChannel(_embedChannelId.GetValueOrDefault(0)); //TODO: Is this text or voice? /// Gets a collection of all channels in this guild. - public IEnumerable Channels => _channels.Select(x => x.Value); + public IEnumerable Channels => _channels.Select(x => Discord.GetChannel(x) as GuildChannel); /// Gets a collection of text channels in this guild. - public IEnumerable TextChannels => _channels.Select(x => x.Value).OfType(); + public IEnumerable TextChannels => _channels.Select(x => Discord.GetChannel(x) as TextChannel); /// Gets a collection of voice channels in this guild. - public IEnumerable VoiceChannels => _channels.Select(x => x.Value).OfType(); + public IEnumerable VoiceChannels => _channels.Select(x => Discord.GetChannel(x) as VoiceChannel); /// Gets a collection of all roles in this guild. public IEnumerable Roles => _roles?.Select(x => x.Value) ?? Enumerable.Empty(); /// Gets a collection of all users in this guild. @@ -185,9 +185,8 @@ namespace Discord.WebSocket /// Gets the channel in this guild with the provided id, or null if not found. public GuildChannel GetChannel(ulong id) { - GuildChannel channel; - if (_channels.TryGetValue(id, out channel)) - return channel; + if (_channels.ContainsKey(id)) + return Discord.GetChannel(id) as GuildChannel; return null; } /// Creates a new text channel.