From 2988b38ea80cb4778e4d0993d827c2cedd2f5481 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Fri, 30 Mar 2018 15:36:58 -0400 Subject: [PATCH 001/106] Resolve mutability issues with EmbedBuilder. (#1010) * Create new entities on each build call. Added Length property to EmbedBuilder. * Resolve Length issues per #1012 --- .../Entities/Messages/EmbedBuilder.cs | 138 +++++++++--------- 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index f5663cea3..62834ebf3 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -1,89 +1,105 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; namespace Discord { public class EmbedBuilder { - private readonly Embed _embed; + private string _title; + private string _description; + private string _url; + private EmbedImage? _image; + private EmbedThumbnail? _thumbnail; + private List _fields; public const int MaxFieldCount = 25; public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 2048; - public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here. + public const int MaxEmbedLength = 6000; public EmbedBuilder() { - _embed = new Embed(EmbedType.Rich); Fields = new List(); } public string Title { - get => _embed.Title; + get => _title; set { if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); - _embed.Title = value; + _title = value; } } - public string Description { - get => _embed.Description; + get => _description; set { if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); - _embed.Description = value; + _description = value; } } public string Url { - get => _embed.Url; + get => _url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); - _embed.Url = value; + _url = value; } } public string ThumbnailUrl { - get => _embed.Thumbnail?.Url; + get => _thumbnail?.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); - _embed.Thumbnail = new EmbedThumbnail(value, null, null, null); + _thumbnail = new EmbedThumbnail(value, null, null, null); } } public string ImageUrl { - get => _embed.Image?.Url; + get => _image?.Url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); - _embed.Image = new EmbedImage(value, null, null, null); + _image = new EmbedImage(value, null, null, null); } } - public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } } - public Color? Color { get => _embed.Color; set { _embed.Color = value; } } - - public EmbedAuthorBuilder Author { get; set; } - public EmbedFooterBuilder Footer { get; set; } - private List _fields; public List Fields { get => _fields; set { - if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); _fields = value; } } + public DateTimeOffset? Timestamp { get; set; } + public Color? Color { get; set; } + public EmbedAuthorBuilder Author { get; set; } + public EmbedFooterBuilder Footer { get; set; } + + public int Length + { + get + { + int titleLength = Title?.Length ?? 0; + int authorLength = Author?.Name?.Length ?? 0; + int descriptionLength = Description?.Length ?? 0; + int footerLength = Footer?.Text?.Length ?? 0; + int fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length); + + return titleLength + authorLength + descriptionLength + footerLength + fieldSum; + } + } + public EmbedBuilder WithTitle(string title) { Title = title; @@ -180,7 +196,6 @@ namespace Discord AddField(field); return this; } - public EmbedBuilder AddField(EmbedFieldBuilder field) { if (Fields.Count >= MaxFieldCount) @@ -195,63 +210,54 @@ namespace Discord { var field = new EmbedFieldBuilder(); action(field); - this.AddField(field); + AddField(field); return this; } public Embed Build() { - _embed.Footer = Footer?.Build(); - _embed.Author = Author?.Build(); + if (Length > MaxEmbedLength) + throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); + var fields = ImmutableArray.CreateBuilder(Fields.Count); for (int i = 0; i < Fields.Count; i++) fields.Add(Fields[i].Build()); - _embed.Fields = fields.ToImmutable(); - if (_embed.Length > MaxEmbedLength) - { - throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); - } - - return _embed; + return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), Footer?.Build(), null, _thumbnail, fields.ToImmutable()); } } public class EmbedFieldBuilder { + private string _name; + private string _value; private EmbedField _field; - public const int MaxFieldNameLength = 256; public const int MaxFieldValueLength = 1024; public string Name { - get => _field.Name; + get => _name; set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); - _field.Name = value; + _name = value; } } public object Value { - get => _field.Value; + get => _value; set { var stringValue = value?.ToString(); if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); - _field.Value = stringValue; + _value = stringValue; } } - public bool IsInline { get => _field.Inline; set { _field.Inline = value; } } - - public EmbedFieldBuilder() - { - _field = new EmbedField(); - } + public bool IsInline { get; set; } public EmbedFieldBuilder WithName(string name) { @@ -270,48 +276,44 @@ namespace Discord } public EmbedField Build() - => _field; + => new EmbedField(Name, Value.ToString(), IsInline); } public class EmbedAuthorBuilder { - private EmbedAuthor _author; - + private string _name; + private string _url; + private string _iconUrl; public const int MaxAuthorNameLength = 256; public string Name { - get => _author.Name; + get => _name; set { if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); - _author.Name = value; + _name = value; } } public string Url { - get => _author.Url; + get => _url; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); - _author.Url = value; + _url = value; } } public string IconUrl { - get => _author.IconUrl; + get => _iconUrl; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); - _author.IconUrl = value; + _iconUrl = value; } } - public EmbedAuthorBuilder() - { - _author = new EmbedAuthor(); - } - public EmbedAuthorBuilder WithName(string name) { Name = name; @@ -329,39 +331,35 @@ namespace Discord } public EmbedAuthor Build() - => _author; + => new EmbedAuthor(Name, Url, IconUrl, null); } public class EmbedFooterBuilder { - private EmbedFooter _footer; + private string _text; + private string _iconUrl; public const int MaxFooterTextLength = 2048; public string Text { - get => _footer.Text; + get => _text; set { if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); - _footer.Text = value; + _text = value; } } public string IconUrl { - get => _footer.IconUrl; + get => _iconUrl; set { if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); - _footer.IconUrl = value; + _iconUrl = value; } } - public EmbedFooterBuilder() - { - _footer = new EmbedFooter(); - } - public EmbedFooterBuilder WithText(string text) { Text = text; @@ -374,6 +372,6 @@ namespace Discord } public EmbedFooter Build() - => _footer; + => new EmbedFooter(Text, IconUrl, null); } } From 6b7c6e9667105d51d589585dcafb43500e722e28 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 30 Mar 2018 16:40:43 -0300 Subject: [PATCH 002/106] Add new overload for AddTypeReader (#1009) --- .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 49 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index e9ce9eb86..1dd66cc77 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -291,7 +291,7 @@ namespace Discord.Commands //We dont have a cached type reader, create one reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); - service.AddTypeReader(paramType, reader); + service.AddTypeReader(paramType, reader, false); return reader; } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f4fbcf8b2..7c7cdf7c5 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -215,10 +215,11 @@ namespace Discord.Commands return true; } - //Type Readers + //Type Readers /// /// Adds a custom to this for the supplied object type. /// If is a , a will also be added. + /// If a default exists for , a warning will be logged and the default will be replaced. /// /// The object type to be read by the . /// An instance of the to be added. @@ -226,17 +227,53 @@ namespace Discord.Commands => AddTypeReader(typeof(T), reader); /// /// Adds a custom to this for the supplied object type. - /// If is a , a for the value type will also be added. + /// If is a , a for the value type will also be added. + /// If a default exists for , a warning will be logged and the default will be replaced. /// /// A instance for the type to be read. /// An instance of the to be added. public void AddTypeReader(Type type, TypeReader reader) { - var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary()); - readers[reader.GetType()] = reader; + if (_defaultTypeReaders.ContainsKey(type)) + _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}. To suppress this message, use the overload that has a Boolean to replace the default one and pass true.").GetAwaiter().GetResult(); + AddTypeReader(type, reader, true); + } + /// + /// Adds a custom to this for the supplied object type. + /// If is a , a will also be added. + /// + /// The object type to be read by the . + /// An instance of the to be added. + /// If should replace the default for if one exists. + public void AddTypeReader(TypeReader reader, bool replaceDefaultTypeReader) + => AddTypeReader(typeof(T), reader, replaceDefaultTypeReader); + /// + /// Adds a custom to this for the supplied object type. + /// If is a , a for the value type will also be added. + /// + /// A instance for the type to be read. + /// An instance of the to be added. + /// If should replace the default for if one exists. + public void AddTypeReader(Type type, TypeReader reader, bool replaceDefaultTypeReader) + { + if (replaceDefaultTypeReader && _defaultTypeReaders.ContainsKey(type)) + { + _defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader); + if (type.GetTypeInfo().IsValueType) + { + var nullableType = typeof(Nullable<>).MakeGenericType(type); + var nullableReader = NullableTypeReader.Create(type, reader); + _defaultTypeReaders.AddOrUpdate(nullableType, nullableReader, (k, v) => nullableReader); + } + } + else + { + var readers = _typeReaders.GetOrAdd(type, x => new ConcurrentDictionary()); + readers[reader.GetType()] = reader; - if (type.GetTypeInfo().IsValueType) - AddNullableTypeReader(type, reader); + if (type.GetTypeInfo().IsValueType) + AddNullableTypeReader(type, reader); + } } internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { From b918712ad2826a64f784a8c5c3882ff737ce814a Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 30 Mar 2018 15:51:28 -0400 Subject: [PATCH 003/106] Cleanup of #1009 --- src/Discord.Net.Commands/CommandService.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 7c7cdf7c5..c880dd454 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -235,7 +235,8 @@ namespace Discord.Commands public void AddTypeReader(Type type, TypeReader reader) { if (_defaultTypeReaders.ContainsKey(type)) - _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}. To suppress this message, use the overload that has a Boolean to replace the default one and pass true.").GetAwaiter().GetResult(); + _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + + $"To suppress this message, use AddTypeReader(reader, true)."); AddTypeReader(type, reader, true); } /// @@ -244,19 +245,19 @@ namespace Discord.Commands /// /// The object type to be read by the . /// An instance of the to be added. - /// If should replace the default for if one exists. - public void AddTypeReader(TypeReader reader, bool replaceDefaultTypeReader) - => AddTypeReader(typeof(T), reader, replaceDefaultTypeReader); + /// If should replace the default for if one exists. + public void AddTypeReader(TypeReader reader, bool replaceDefault) + => AddTypeReader(typeof(T), reader, replaceDefault); /// /// Adds a custom to this for the supplied object type. /// If is a , a for the value type will also be added. /// /// A instance for the type to be read. /// An instance of the to be added. - /// If should replace the default for if one exists. - public void AddTypeReader(Type type, TypeReader reader, bool replaceDefaultTypeReader) + /// If should replace the default for if one exists. + public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault) { - if (replaceDefaultTypeReader && _defaultTypeReaders.ContainsKey(type)) + if (replaceDefault && _defaultTypeReaders.ContainsKey(type)) { _defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader); if (type.GetTypeInfo().IsValueType) From c67db8896113615f2404cd7b5c2a121528c3bb44 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Sun, 25 Mar 2018 13:25:49 -0500 Subject: [PATCH 004/106] Cleaned up and refactored slightly --- src/Discord.Net.Commands/Info/CommandInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index f0d406e8d..6e74c8abc 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -165,11 +165,11 @@ namespace Discord.Commands switch (RunMode) { case RunMode.Sync: //Always sync - return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); + return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); case RunMode.Async: //Always async var t2 = Task.Run(async () => { - await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); + await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); }); break; } @@ -181,7 +181,7 @@ namespace Discord.Commands } } - private async Task ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services) + private async Task ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services) { await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); try From 109f663a9adc450823f9cd91da03812256553f51 Mon Sep 17 00:00:00 2001 From: Fyers Date: Sun, 1 Apr 2018 20:02:50 +0200 Subject: [PATCH 005/106] added UserDefaultAvatar to IUser (#973) * added UserDefaultAvatar to IUser * pass ushort as discriminator * removed unneeded ushort.parse --- src/Discord.Net.Core/CDN.cs | 19 ++++++++++++++----- src/Discord.Net.Core/Entities/Users/IUser.cs | 2 ++ .../Entities/Users/RestUser.cs | 5 ++++- .../Entities/Users/SocketUser.cs | 13 ++++++++----- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index f23f55238..52b9a2878 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -13,6 +13,10 @@ namespace Discord string extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } + public static string GetDefaultUserAvatarUrl(ushort discriminator) + { + return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png"; + } public static string GetGuildIconUrl(ulong guildId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; public static string GetGuildSplashUrl(ulong guildId, string splashId) @@ -37,11 +41,16 @@ namespace Discord format = imageId.StartsWith("a_") ? ImageFormat.Gif : ImageFormat.Png; switch (format) { - case ImageFormat.Gif: return "gif"; - case ImageFormat.Jpeg: return "jpeg"; - case ImageFormat.Png: return "png"; - case ImageFormat.WebP: return "webp"; - default: throw new ArgumentException(nameof(format)); + case ImageFormat.Gif: + return "gif"; + case ImageFormat.Jpeg: + return "jpeg"; + case ImageFormat.Png: + return "png"; + case ImageFormat.WebP: + return "webp"; + default: + throw new ArgumentException(nameof(format)); } } } diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index e3f270f6f..c5cce7a25 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -8,6 +8,8 @@ namespace Discord string AvatarId { get; } /// Gets the url to this user's avatar. string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); + /// Gets the url to this user's default avatar. + string GetDefaultAvatarUrl(); /// Gets the per-username unique id for this user. string Discriminator { get; } /// Gets the per-username unique id for this user. diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index c6cf6103a..c484986b1 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.User; @@ -60,6 +60,9 @@ namespace Discord.Rest public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + public string GetDefaultAvatarUrl() + => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 58d5c62a1..00899d47e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Threading.Tasks; using Model = Discord.API.User; @@ -37,23 +37,23 @@ namespace Discord.WebSocket { var newVal = ushort.Parse(model.Discriminator.Value); if (newVal != DiscriminatorValue) - { + { DiscriminatorValue = ushort.Parse(model.Discriminator.Value); hasChanges = true; } } if (model.Bot.IsSpecified && model.Bot.Value != IsBot) - { + { IsBot = model.Bot.Value; hasChanges = true; } if (model.Username.IsSpecified && model.Username.Value != Username) - { + { Username = model.Username.Value; hasChanges = true; } return hasChanges; - } + } public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; @@ -61,6 +61,9 @@ namespace Discord.WebSocket public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + public string GetDefaultAvatarUrl() + => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + public override string ToString() => $"{Username}#{Discriminator}"; private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; From b8b59d97ae788a9d971258010bced393dea365f6 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 6 Apr 2018 23:28:54 +0200 Subject: [PATCH 006/106] Forward all embed-related types for non-updated addons (#1021) --- src/Discord.Net.Rest/AssemblyInfo.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs index 126365e47..9c90919af 100644 --- a/src/Discord.Net.Rest/AssemblyInfo.cs +++ b/src/Discord.Net.Rest/AssemblyInfo.cs @@ -6,5 +6,17 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Discord.Net.Commands")] [assembly: InternalsVisibleTo("Discord.Net.Tests")] +[assembly: TypeForwardedTo(typeof(Discord.Embed))] [assembly: TypeForwardedTo(typeof(Discord.EmbedBuilder))] [assembly: TypeForwardedTo(typeof(Discord.EmbedBuilderExtensions))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthor))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthorBuilder))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedField))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedFieldBuilder))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedFooter))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedFooterBuilder))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedImage))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedProvider))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedThumbnail))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedType))] +[assembly: TypeForwardedTo(typeof(Discord.EmbedVideo))] From c618cb3ccd6e9ebf2d5b85e872379272487f6395 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Sat, 21 Apr 2018 13:41:16 -0500 Subject: [PATCH 007/106] Fixes RetryMode.RetryRatelimit being ignored (#1036) --- .../Net/Queue/RequestQueueBucket.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 2d96ca796..3346681b5 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -163,7 +163,7 @@ namespace Discord.Net.Queue if (!isRateLimited) throw new TimeoutException(); else - throw new RateLimitedException(request); + ThrowRetryLimit(request); } lock (_lock) @@ -181,13 +181,12 @@ namespace Discord.Net.Queue await _queue.RaiseRateLimitTriggered(Id, null).ConfigureAwait(false); } - if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0) - throw new RateLimitedException(request); + ThrowRetryLimit(request); if (resetAt.HasValue) { if (resetAt > timeoutAt) - throw new RateLimitedException(request); + ThrowRetryLimit(request); int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); @@ -198,7 +197,7 @@ namespace Discord.Net.Queue else { if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) - throw new RateLimitedException(request); + ThrowRetryLimit(request); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); #endif @@ -309,5 +308,11 @@ namespace Discord.Net.Queue } } } + + private void ThrowRetryLimit(RestRequest request) + { + if ((request.Options.RetryMode & RetryMode.RetryRatelimit) == 0) + throw new RateLimitedException(request); + } } } From 510f4745ea5f6d6fe3e802249adc79945733dcb5 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sun, 22 Apr 2018 02:43:00 +0800 Subject: [PATCH 008/106] Update VoiceRegion model (#1030) --- .../Entities/Guilds/IVoiceRegion.cs | 12 ++++++------ src/Discord.Net.Rest/API/Common/VoiceRegion.cs | 10 +++++----- .../Entities/Guilds/RestVoiceRegion.cs | 15 ++++++++++----- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs index 1a76287d8..67bedbc3d 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs @@ -1,4 +1,4 @@ -namespace Discord +namespace Discord { public interface IVoiceRegion { @@ -10,9 +10,9 @@ bool IsVip { get; } /// Returns true if this voice region is the closest to your machine. bool IsOptimal { get; } - /// Gets an example hostname for this voice region. - string SampleHostname { get; } - /// Gets an example port for this voice region. - int SamplePort { get; } + /// Returns true if this is a deprecated voice region (avoid switching to these). + bool IsDeprecated { get; } + /// Returns true if this is a custom voice region (used for events/etc) + bool IsCustom { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs index 5f31e8f64..606af07bd 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -13,9 +13,9 @@ namespace Discord.API public bool IsVip { get; set; } [JsonProperty("optimal")] public bool IsOptimal { get; set; } - [JsonProperty("sample_hostname")] - public string SampleHostname { get; set; } - [JsonProperty("sample_port")] - public int SamplePort { get; set; } + [JsonProperty("deprecated")] + public bool IsDeprecated { get; set; } + [JsonProperty("custom")] + public bool IsCustom { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs index 47fd2cd19..4e0c3c1ee 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System.Diagnostics; using Model = Discord.API.VoiceRegion; @@ -7,11 +7,16 @@ namespace Discord [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RestVoiceRegion : RestEntity, IVoiceRegion { + /// public string Name { get; private set; } + /// public bool IsVip { get; private set; } + /// public bool IsOptimal { get; private set; } - public string SampleHostname { get; private set; } - public int SamplePort { get; private set; } + /// + public bool IsDeprecated { get; private set; } + /// + public bool IsCustom { get; private set; } internal RestVoiceRegion(BaseDiscordClient client, string id) : base(client, id) @@ -28,8 +33,8 @@ namespace Discord Name = model.Name; IsVip = model.IsVip; IsOptimal = model.IsOptimal; - SampleHostname = model.SampleHostname; - SamplePort = model.SamplePort; + IsDeprecated = model.IsDeprecated; + IsCustom = model.IsCustom; } public override string ToString() => Name; From a4d1e2bc14c09bf0db02b1291da216ba00bb5268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Sun, 22 Apr 2018 03:45:26 +0900 Subject: [PATCH 009/106] Fix build fails when built on macOS and Linux (#1026) * Add EditorConfig * Add basic .NET style rules * Add naming rules * Add some more extension * Add target of the internal fields * Fix build fails when built on macOS and Linux * Use 'Condition' attributes refs: https://github.com/RogueException/Discord.Net/pull/1026/files/6f29dda78b35c4ce6296a0250d88ed223c5b363d#r181371650 --- src/Discord.Net.Core/Discord.Net.Core.csproj | 5 +++-- src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj | 5 +++-- src/Discord.Net.Rest/Discord.Net.Rest.csproj | 5 +++-- src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index bce577ddd..7565fa178 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -4,11 +4,12 @@ Discord.Net.Core Discord The core components for the Discord.Net library. - net45;netstandard1.1;netstandard1.3 + net45;netstandard1.1;netstandard1.3 + netstandard1.1;netstandard1.3 - \ No newline at end of file + diff --git a/src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj b/src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj index 43d627c99..8c56a5fb4 100644 --- a/src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj +++ b/src/Discord.Net.DebugTools/Discord.Net.DebugTools.csproj @@ -4,9 +4,10 @@ Discord.Net.DebugTools Discord A Discord.Net extension adding some helper classes for diagnosing issues. - net45;netstandard1.3 + net45;netstandard1.3 + netstandard1.3 - \ No newline at end of file + diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 29f79e410..0eb07a4b2 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -4,7 +4,8 @@ Discord.Net.Rest Discord.Rest A core Discord.Net library containing the REST client and models. - net45;netstandard1.1;netstandard1.3 + net45;netstandard1.1;netstandard1.3 + netstandard1.1;netstandard1.3 @@ -16,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index e4de43e51..251cdb18b 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -4,7 +4,8 @@ Discord.Net.WebSocket Discord.WebSocket A core Discord.Net library containing the WebSocket client and models. - net45;netstandard1.1;netstandard1.3 + net45;netstandard1.1;netstandard1.3 + netstandard1.1;netstandard1.3 true @@ -14,4 +15,4 @@ - \ No newline at end of file + From a3ce80c1dcaecc255d1650b9e227d2f30e440d0c Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sun, 29 Apr 2018 23:08:43 +0800 Subject: [PATCH 010/106] Fix Embed.Length behavior (#1012) - This commit splits up the get statement lines for better analysis. --- src/Discord.Net.Core/Entities/Messages/Embed.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index 5fae7acde..dc62066eb 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -57,7 +57,18 @@ namespace Discord Fields = fields; } - public int Length => Title?.Length + Author?.Name?.Length + Description?.Length + Footer?.Text?.Length + Fields.Sum(f => f.Name.Length + f.Value.ToString().Length) ?? 0; + public int Length + { + get + { + int titleLength = Title?.Length ?? 0; + int authorLength = Author?.Name?.Length ?? 0; + int descriptionLength = Description?.Length ?? 0; + int footerLength = Footer?.Text?.Length ?? 0; + int fieldSum = Fields.Sum(f => f.Name?.Length + f.Value?.ToString().Length) ?? 0; + return titleLength + authorLength + descriptionLength + footerLength + fieldSum; + } + } public override string ToString() => Title; private string DebuggerDisplay => $"{Title} ({Type})"; From 6d3010065fdeddaf58701c6a59d0eb0d0749a7fd Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sun, 29 Apr 2018 17:10:00 +0200 Subject: [PATCH 011/106] Allow setting IgnoreExtraArgs on an individual basis (#998) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow setting IgnoreExtraArgs on an individual basis * Remove passing in the flag as a separate parameter * VS plz * Push the RunMode setting out to its own attribute, because fox wants consistency. Bonus: Removes the need for that godawful 'RunMode.Default'. * Revert previous commit * Fox doesn't like module-wide switches 😒 --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 1 + src/Discord.Net.Commands/Builders/CommandBuilder.cs | 6 +++--- src/Discord.Net.Commands/Builders/ModuleBuilder.cs | 1 - src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 1 + src/Discord.Net.Commands/CommandParser.cs | 6 +++--- src/Discord.Net.Commands/CommandService.cs | 1 - src/Discord.Net.Commands/CommandServiceConfig.cs | 6 ------ src/Discord.Net.Commands/Info/CommandInfo.cs | 4 +++- src/Discord.Net.Commands/Info/ModuleInfo.cs | 5 ----- 9 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index 5f8e9ceaf..a0fcf3e4a 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -7,6 +7,7 @@ namespace Discord.Commands { public string Text { get; } public RunMode RunMode { get; set; } = RunMode.Default; + public bool? IgnoreExtraArgs { get; set; } public CommandAttribute() { diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index b6d002c70..17a170775 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands.Builders { @@ -22,6 +21,7 @@ namespace Discord.Commands.Builders public string PrimaryAlias { get; set; } public RunMode RunMode { get; set; } public int Priority { get; set; } + public bool IgnoreExtraArgs { get; set; } public IReadOnlyList Preconditions => _preconditions; public IReadOnlyList Parameters => _parameters; @@ -140,4 +140,4 @@ namespace Discord.Commands.Builders return new CommandInfo(this, info, service); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 1809c2c63..0ada5a9c2 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands.Builders { diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 1dd66cc77..cbe02aafb 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -158,6 +158,7 @@ namespace Discord.Commands builder.AddAliases(command.Text); builder.RunMode = command.RunMode; builder.Name = builder.Name ?? command.Text; + builder.IgnoreExtraArgs = command.IgnoreExtraArgs ?? service._ignoreExtraArgs; break; case NameAttribute name: builder.Name = name.Text; diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index d65d99349..64daf841e 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Immutable; using System.Text; using System.Threading.Tasks; @@ -14,7 +14,7 @@ namespace Discord.Commands QuotedParameter } - public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos) + public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) { ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); @@ -110,7 +110,7 @@ namespace Discord.Commands { if (curParam == null) { - if (ignoreExtraArgs) + if (command.IgnoreExtraArgs) break; else return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index c880dd454..c996f7334 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Discord.Commands.Builders; using Discord.Logging; diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 77c5b2262..f5cd14fef 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -20,11 +20,5 @@ namespace Discord.Commands /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; - - ///// Gets or sets the to use. - //public IServiceProvider ServiceProvider { get; set; } = null; - - ///// Gets or sets a factory function for the to use. - //public Func ServiceProviderFactory { get; set; } = null; } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 6e74c8abc..1a9cf69c5 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -27,6 +27,7 @@ namespace Discord.Commands public string Remarks { get; } public int Priority { get; } public bool HasVarArgs { get; } + public bool IgnoreExtraArgs { get; } public RunMode RunMode { get; } public IReadOnlyList Aliases { get; } @@ -63,6 +64,7 @@ namespace Discord.Commands Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; + IgnoreExtraArgs = builder.IgnoreExtraArgs; _action = builder.Callback; _commandService = service; @@ -119,7 +121,7 @@ namespace Discord.Commands return ParseResult.FromError(preconditionResult); string input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0).ConfigureAwait(false); + return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false); } public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 5a7f9208e..7c144599b 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; -using System.Reflection; using Discord.Commands.Builders; namespace Discord.Commands @@ -23,8 +22,6 @@ namespace Discord.Commands public ModuleInfo Parent { get; } public bool IsSubmodule => Parent != null; - //public TypeInfo TypeInfo { get; } - internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) { Service = service; @@ -35,8 +32,6 @@ namespace Discord.Commands Group = builder.Group; Parent = parent; - //TypeInfo = builder.TypeInfo; - Aliases = BuildAliases(builder, service).ToImmutableArray(); Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray(); Preconditions = BuildPreconditions(builder).ToImmutableArray(); From 7022149536ea1c9a92d6dce846cdf7d1df76772e Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sun, 29 Apr 2018 23:11:06 +0800 Subject: [PATCH 012/106] Fix/Implement various invite-related behaviors (#1023) * Initial support for invite member count arg * Fix IDiscordClient#GetInviteAsync behavior - Previously, the GetInviteAsync method would return null since it couldn't be parsed as a simple RestInvite object. The object should be a RestInviteMetadata instead. * Fix methods that didn't comply with the interface * Change with_counts REST behaviour * Remove unnecessary JSON prop * Remove AcceptAsync --- src/Discord.Net.Core/Entities/Invites/IInvite.cs | 9 +++++---- src/Discord.Net.Core/IDiscordClient.cs | 2 +- src/Discord.Net.Rest/API/Common/Invite.cs | 6 +++++- src/Discord.Net.Rest/API/Rest/GetInviteParams.cs | 7 +++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 2 +- src/Discord.Net.Rest/ClientHelper.cs | 12 ++++++++---- src/Discord.Net.Rest/DiscordRestApiClient.cs | 13 ++++--------- src/Discord.Net.Rest/DiscordRestClient.cs | 8 ++++---- .../Entities/Invites/InviteHelper.cs | 7 +------ .../Entities/Invites/RestInvite.cs | 15 ++++++++++----- src/Discord.Net.WebSocket/BaseSocketClient.cs | 8 ++++---- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 4 ++-- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 ++-- 13 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Rest/GetInviteParams.cs diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 73555e453..0ebb65679 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord { @@ -22,8 +22,9 @@ namespace Discord ulong GuildId { get; } /// Gets the name of the guild this invite is linked to. string GuildName { get; } - - /// Accepts this invite and joins the target guild. This will fail on bot accounts. - Task AcceptAsync(RequestOptions options = null); + /// Gets the approximated count of online members in the guild. + int? PresenceCount { get; } + /// Gets the approximated count of total members in the guild. + int? MemberCount { get; } } } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index a383c37da..083c0b512 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -27,7 +27,7 @@ namespace Discord Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); - Task GetInviteAsync(string inviteId, RequestOptions options = null); + Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null); Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetUserAsync(string username, string discriminator, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index 67a318c5a..1b35da870 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -11,5 +11,9 @@ namespace Discord.API public InviteGuild Guild { get; set; } [JsonProperty("channel")] public InviteChannel Channel { get; set; } + [JsonProperty("approximate_presence_count")] + public Optional PresenceCount { get; set; } + [JsonProperty("approximate_member_count")] + public Optional MemberCount { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs b/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs new file mode 100644 index 000000000..cb8d8f7fe --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs @@ -0,0 +1,7 @@ +namespace Discord.API.Rest +{ + internal class GetInviteParams + { + public Optional WithCounts { get; set; } + } +} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index f8642b96c..db8e2e691 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -148,7 +148,7 @@ namespace Discord.Rest Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); - Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) => Task.FromResult(null); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 08305f857..a1f8ece69 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -50,12 +50,16 @@ namespace Discord.Rest return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); } - public static async Task GetInviteAsync(BaseDiscordClient client, - string inviteId, RequestOptions options) + public static async Task GetInviteAsync(BaseDiscordClient client, + string inviteId, bool withCount, RequestOptions options) { - var model = await client.ApiClient.GetInviteAsync(inviteId, options).ConfigureAwait(false); + var args = new GetInviteParams + { + WithCounts = withCount + }; + var model = await client.ApiClient.GetInviteAsync(inviteId, args, options).ConfigureAwait(false); if (model != null) - return RestInvite.Create(client, null, null, model); + return RestInviteMetadata.Create(client, null, null, model); return null; } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f0c4358ad..7ec30c3aa 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -897,7 +897,7 @@ namespace Discord.API } //Guild Invites - public async Task GetInviteAsync(string inviteId, RequestOptions options = null) + public async Task GetInviteAsync(string inviteId, GetInviteParams args, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); @@ -910,9 +910,11 @@ namespace Discord.API if (index >= 0) inviteId = inviteId.Substring(index + 1); + var withCounts = args.WithCounts.GetValueOrDefault(false); + try { - return await SendAsync("GET", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"invites/{inviteId}?with_counts={withCounts}", new BucketIds(), options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } @@ -950,13 +952,6 @@ namespace Discord.API return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } - public async Task AcceptInviteAsync(string inviteId, RequestOptions options = null) - { - Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); - options = RequestOptions.CreateOrClone(options); - - await SendAsync("POST", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); - } //Guild Members public async Task GetGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 8850da3a5..3a596624d 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -56,8 +56,8 @@ namespace Discord.Rest => ClientHelper.GetConnectionsAsync(this, options); /// - public Task GetInviteAsync(string inviteId, RequestOptions options = null) - => ClientHelper.GetInviteAsync(this, inviteId, options); + public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) + => ClientHelper.GetInviteAsync(this, inviteId, withCount, options); /// public Task GetGuildAsync(ulong id, RequestOptions options = null) @@ -131,8 +131,8 @@ namespace Discord.Rest async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) + => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs index 80a49e34e..ebcd93777 100644 --- a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -1,14 +1,9 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Discord.Rest { internal static class InviteHelper { - public static async Task AcceptAsync(IInvite invite, BaseDiscordClient client, - RequestOptions options) - { - await client.ApiClient.AcceptInviteAsync(invite.Code, options).ConfigureAwait(false); - } public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 900d1f0ac..18698c626 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.Invite; namespace Discord.Rest @@ -10,6 +11,8 @@ namespace Discord.Rest { public string ChannelName { get; private set; } public string GuildName { get; private set; } + public int? PresenceCount { get; private set; } + public int? MemberCount { get; private set; } public ulong ChannelId { get; private set; } public ulong GuildId { get; private set; } internal IChannel Channel { get; private set; } @@ -36,19 +39,21 @@ namespace Discord.Rest ChannelId = model.Channel.Id; GuildName = model.Guild.Name; ChannelName = model.Channel.Name; + MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; + PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; } public async Task UpdateAsync(RequestOptions options = null) { - var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); + var args = new GetInviteParams(); + if (MemberCount != null || PresenceCount != null) + args.WithCounts = true; + var model = await Discord.ApiClient.GetInviteAsync(Code, args, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options = null) => InviteHelper.DeleteAsync(this, Discord, options); - public Task AcceptAsync(RequestOptions options = null) - => InviteHelper.AcceptAsync(this, Discord, options); - public override string ToString() => Url; private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index 923b2c23b..fb82fe14a 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -55,8 +55,8 @@ namespace Discord.WebSocket public Task> GetConnectionsAsync(RequestOptions options = null) => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); /// - public Task GetInviteAsync(string inviteId, RequestOptions options = null) - => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); + public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) + => ClientHelper.GetInviteAsync(this, inviteId, withCount, options ?? RequestOptions.Default); // IDiscordClient async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) @@ -70,8 +70,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) + => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index ad89067df..45d76e61a 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -327,8 +327,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) + => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 593f796c2..0188f7e9f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1796,8 +1796,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) + => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); From 9b29c004fa8216cff1bd593f6eb90936656886a8 Mon Sep 17 00:00:00 2001 From: Allan Date: Sun, 29 Apr 2018 17:11:44 +0200 Subject: [PATCH 013/106] Fixed GetReactionUsersAsync that was not using limit and afterUserId arguments. (#1017) --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 7ec30c3aa..042ae9970 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -626,7 +626,7 @@ namespace Discord.API ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); var ids = new BucketIds(channelId: channelId); - Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}"; + Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) From 3631886d2bf63b2a2e6175c185f51598612becf3 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sun, 29 Apr 2018 23:14:34 +0800 Subject: [PATCH 014/106] Resolves #1024 (#1031) - Replace 'var _' with simple '_' discard as well. --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 0188f7e9f..18413e840 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -479,14 +479,14 @@ namespace Discord.WebSocket await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); }); - var _ = _connection.CompleteAsync(); + _ = _connection.CompleteAsync(); } break; case "RESUMED": { await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); - var _ = _connection.CompleteAsync(); + _ = _connection.CompleteAsync(); //Notify the client that these guilds are available again foreach (var guild in State.Guilds) @@ -1095,8 +1095,10 @@ namespace Discord.WebSocket else if (channel is SocketGroupChannel) author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); else + { await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); - return; + return; + } } var msg = SocketMessage.Create(this, State, author, channel, data); From 73dc884d47bbff897de234351b39b97979cc9f14 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sun, 29 Apr 2018 23:15:24 +0800 Subject: [PATCH 015/106] Improve SpotifyGame (#1039) * Initial commit * Add TrackUrl * Rename AlbumArt to AlbumArtUrl for consistency --- src/Discord.Net.Core/CDN.cs | 2 ++ .../Entities/Activities/SpotifyGame.cs | 13 ++++++++----- .../Extensions/EntityExtensions.cs | 10 +++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 52b9a2878..6bac241aa 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -34,6 +34,8 @@ namespace Discord public static string GetSpotifyAlbumArtUrl(string albumArtId) => $"https://i.scdn.co/image/{albumArtId}"; + public static string GetSpotifyDirectUrl(string trackId) + => $"https://open.spotify.com/track/{trackId}"; private static string FormatToExtension(ImageFormat format, string imageId) { diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs index a20384242..8b966d68b 100644 --- a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -7,17 +7,20 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SpotifyGame : Game { - public IEnumerable Artists { get; internal set; } - public string AlbumArt { get; internal set; } + public IReadOnlyCollection Artists { get; internal set; } public string AlbumTitle { get; internal set; } public string TrackTitle { get; internal set; } - public string SyncId { get; internal set; } - public string SessionId { get; internal set; } public TimeSpan? Duration { get; internal set; } + public string TrackId { get; internal set; } + public string SessionId { get; internal set; } + + public string AlbumArtUrl { get; internal set; } + public string TrackUrl { get; internal set; } + internal SpotifyGame() { } - public override string ToString() => Name; + public override string ToString() => $"{string.Join(", ", Artists)} - {TrackTitle} ({Duration})"; private string DebuggerDisplay => $"{Name} (Spotify)"; } } diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index ff395a932..e8dc4b5f0 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -1,3 +1,6 @@ +using System.Collections.Immutable; +using System.Linq; + namespace Discord.WebSocket { internal static class EntityExtensions @@ -15,12 +18,13 @@ namespace Discord.WebSocket { Name = model.Name, SessionId = model.SessionId.GetValueOrDefault(), - SyncId = model.SyncId.Value, + TrackId = model.SyncId.Value, + TrackUrl = CDN.GetSpotifyDirectUrl(model.SyncId.Value), AlbumTitle = albumText, TrackTitle = model.Details.GetValueOrDefault(), - Artists = model.State.GetValueOrDefault()?.Split(';'), + Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), Duration = timestamps?.End - timestamps?.Start, - AlbumArt = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, + AlbumArtUrl = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, Type = ActivityType.Listening }; } From 217ec34ef05ef3f022e20394a62801546b2ce5d2 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 29 Apr 2018 11:16:27 -0400 Subject: [PATCH 016/106] Add sample bots to the solution (#972) * Add sample bots to the solution * Fix missing attributes, show use of preconditions --- Discord.Net.sln | 36 +++++++++- .../01_basic_ping_bot.csproj | 12 ++++ samples/01_basic_ping_bot/Program.cs | 69 +++++++++++++++++++ .../02_commands_framework.csproj | 17 +++++ .../Modules/PublicModule.cs | 63 +++++++++++++++++ samples/02_commands_framework/Program.cs | 60 ++++++++++++++++ .../Services/CommandHandlingService.cs | 49 +++++++++++++ .../Services/PictureService.cs | 20 ++++++ 8 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 samples/01_basic_ping_bot/01_basic_ping_bot.csproj create mode 100644 samples/01_basic_ping_bot/Program.cs create mode 100644 samples/02_commands_framework/02_commands_framework.csproj create mode 100644 samples/02_commands_framework/Modules/PublicModule.cs create mode 100644 samples/02_commands_framework/Program.cs create mode 100644 samples/02_commands_framework/Services/CommandHandlingService.cs create mode 100644 samples/02_commands_framework/Services/PictureService.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index daf902b96..9bb940d8c 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.0 +VisualStudioVersion = 15.0.27004.2009 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" EndProject @@ -22,7 +22,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Analyzers", "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj", "{BBA8E7FB-C834-40DC-822F-B112CB7F0140}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers", "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj", "{BBA8E7FB-C834-40DC-822F-B112CB7F0140}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01_basic_ping_bot", "samples\01_basic_ping_bot\01_basic_ping_bot.csproj", "{F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02_commands_framework", "samples\02_commands_framework\02_commands_framework.csproj", "{4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -130,6 +136,30 @@ Global {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x64.Build.0 = Release|Any CPU {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.ActiveCfg = Release|Any CPU {BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.Build.0 = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x64.Build.0 = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Debug|x86.Build.0 = Debug|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|Any CPU.Build.0 = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x64.ActiveCfg = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x64.Build.0 = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x86.ActiveCfg = Release|Any CPU + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E}.Release|x86.Build.0 = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x64.Build.0 = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Debug|x86.Build.0 = Debug|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|Any CPU.Build.0 = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x64.ActiveCfg = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x64.Build.0 = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x86.ActiveCfg = Release|Any CPU + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -141,6 +171,8 @@ Global {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} {BBA8E7FB-C834-40DC-822F-B112CB7F0140} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} + {F2FF84FB-F6AD-47E5-9EE5-18206CAE136E} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} + {4E1F1F40-B1DD-40C9-A4B1-A2046A4C9C76} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} diff --git a/samples/01_basic_ping_bot/01_basic_ping_bot.csproj b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj new file mode 100644 index 000000000..5484e3d55 --- /dev/null +++ b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.0 + + + + + + + diff --git a/samples/01_basic_ping_bot/Program.cs b/samples/01_basic_ping_bot/Program.cs new file mode 100644 index 000000000..0fcc52b85 --- /dev/null +++ b/samples/01_basic_ping_bot/Program.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; + +namespace _01_basic_ping_bot +{ + // This is a minimal, barebones example of using Discord.Net + // + // If writing a bot with commands, we recommend using the Discord.Net.Commands + // framework, rather than handling commands yourself, like we do in this sample. + // + // You can find samples of using the command framework: + // - Here, under the 02_commands_framework sample + // - https://github.com/foxbot/DiscordBotBase - a barebones bot template + // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library + class Program + { + private DiscordSocketClient _client; + + // Discord.Net heavily utilizes TAP for async, so we create + // an asynchronous context from the beginning. + static void Main(string[] args) + => new Program().MainAsync().GetAwaiter().GetResult(); + + public async Task MainAsync() + { + _client = new DiscordSocketClient(); + + _client.Log += LogAsync; + _client.Ready += ReadyAsync; + _client.MessageReceived += MessageReceivedAsync; + + // Tokens should be considered secret data, and never hard-coded. + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); + await _client.StartAsync(); + + // Block the program until it is closed. + await Task.Delay(-1); + } + + private Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); + return Task.CompletedTask; + } + + // The Ready event indicates that the client has opened a + // connection and it is now safe to access the cache. + private Task ReadyAsync() + { + Console.WriteLine($"{_client.CurrentUser} is connected!"); + + return Task.CompletedTask; + } + + // This is not the recommmended way to write a bot - consider + // reading over the Commands Framework sample. + private async Task MessageReceivedAsync(SocketMessage message) + { + // The bot should never respond to itself. + if (message.Author.Id == _client.CurrentUser.Id) + return; + + if (message.Content == "!ping") + await message.Channel.SendMessageAsync("pong!"); + } + } +} diff --git a/samples/02_commands_framework/02_commands_framework.csproj b/samples/02_commands_framework/02_commands_framework.csproj new file mode 100644 index 000000000..77fdc65e1 --- /dev/null +++ b/samples/02_commands_framework/02_commands_framework.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + diff --git a/samples/02_commands_framework/Modules/PublicModule.cs b/samples/02_commands_framework/Modules/PublicModule.cs new file mode 100644 index 000000000..f30dfd73f --- /dev/null +++ b/samples/02_commands_framework/Modules/PublicModule.cs @@ -0,0 +1,63 @@ +using System.IO; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using _02_commands_framework.Services; + +namespace _02_commands_framework.Modules +{ + // Modules must be public and inherit from an IModuleBase + public class PublicModule : ModuleBase + { + // Dependency Injection will fill this value in for us + public PictureService PictureService { get; set; } + + [Command("ping")] + [Alias("pong", "hello")] + public Task PingAsync() + => ReplyAsync("pong!"); + + [Command("cat")] + public async Task CatAsync() + { + // Get a stream containing an image of a cat + var stream = await PictureService.GetCatPictureAsync(); + // Streams must be seeked to their beginning before being uploaded! + stream.Seek(0, SeekOrigin.Begin); + await Context.Channel.SendFileAsync(stream, "cat.png"); + } + + // Get info on a user, or the user who invoked the command if one is not specified + [Command("userinfo")] + public async Task UserInfoAsync(IUser user = null) + { + user = user ?? Context.User; + + await ReplyAsync(user.ToString()); + } + + // Ban a user + [Command("ban")] + [RequireContext(ContextType.Guild)] + // make sure the user invoking the command can ban + [RequireUserPermission(GuildPermission.BanMembers)] + // make sure the bot itself can ban + [RequireBotPermission(GuildPermission.BanMembers)] + public async Task BanUserAsync(IGuildUser user, [Remainder] string reason = null) + { + await user.Guild.AddBanAsync(user, reason: reason); + await ReplyAsync("ok!"); + } + + // [Remainder] takes the rest of the command's arguments as one argument, rather than splitting every space + [Command("echo")] + public Task EchoAsync([Remainder] string text) + // Insert a ZWSP before the text to prevent triggering other bots! + => ReplyAsync('\u200B' + text); + + // 'params' will parse space-separated elements into a list + [Command("list")] + public Task ListAsync(params string[] objects) + => ReplyAsync("You listed: " + string.Join("; ", objects)); + } +} diff --git a/samples/02_commands_framework/Program.cs b/samples/02_commands_framework/Program.cs new file mode 100644 index 000000000..3fed652d3 --- /dev/null +++ b/samples/02_commands_framework/Program.cs @@ -0,0 +1,60 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord; +using Discord.WebSocket; +using Discord.Commands; +using _02_commands_framework.Services; + +namespace _02_commands_framework +{ + // This is a minimal example of using Discord.Net's command + // framework - by no means does it show everything the framework + // is capable of. + // + // You can find samples of using the command framework: + // - Here, under the 02_commands_framework sample + // - https://github.com/foxbot/DiscordBotBase - a barebones bot template + // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library + class Program + { + static void Main(string[] args) + => new Program().MainAsync().GetAwaiter().GetResult(); + + public async Task MainAsync() + { + var services = ConfigureServices(); + + var client = services.GetRequiredService(); + + client.Log += LogAsync; + services.GetRequiredService().Log += LogAsync; + + await client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token")); + await client.StartAsync(); + + await services.GetRequiredService().InitializeAsync(); + + await Task.Delay(-1); + } + + private Task LogAsync(LogMessage log) + { + Console.WriteLine(log.ToString()); + + return Task.CompletedTask; + } + + private IServiceProvider ConfigureServices() + { + return new ServiceCollection() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + } + } +} diff --git a/samples/02_commands_framework/Services/CommandHandlingService.cs b/samples/02_commands_framework/Services/CommandHandlingService.cs new file mode 100644 index 000000000..fc253eed3 --- /dev/null +++ b/samples/02_commands_framework/Services/CommandHandlingService.cs @@ -0,0 +1,49 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord; +using Discord.Commands; +using Discord.WebSocket; + +namespace _02_commands_framework.Services +{ + public class CommandHandlingService + { + private readonly CommandService _commands; + private readonly DiscordSocketClient _discord; + private readonly IServiceProvider _services; + + public CommandHandlingService(IServiceProvider services) + { + _commands = services.GetRequiredService(); + _discord = services.GetRequiredService(); + _services = services; + + _discord.MessageReceived += MessageReceivedAsync; + } + + public async Task InitializeAsync() + { + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } + + public async Task MessageReceivedAsync(SocketMessage rawMessage) + { + // Ignore system messages, or messages from other bots + if (!(rawMessage is SocketUserMessage message)) return; + if (message.Source != MessageSource.User) return; + + // This value holds the offset where the prefix ends + var argPos = 0; + if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return; + + var context = new SocketCommandContext(_discord, message); + var result = await _commands.ExecuteAsync(context, argPos, _services); + + if (result.Error.HasValue && + result.Error.Value != CommandError.UnknownCommand) // it's bad practice to send 'unknown command' errors + await context.Channel.SendMessageAsync(result.ToString()); + } + } +} diff --git a/samples/02_commands_framework/Services/PictureService.cs b/samples/02_commands_framework/Services/PictureService.cs new file mode 100644 index 000000000..dda818cc3 --- /dev/null +++ b/samples/02_commands_framework/Services/PictureService.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace _02_commands_framework.Services +{ + public class PictureService + { + private readonly HttpClient _http; + + public PictureService(HttpClient http) + => _http = http; + + public async Task GetCatPictureAsync() + { + var resp = await _http.GetAsync("https://cataas.com/cat"); + return await resp.Content.ReadAsStreamAsync(); + } + } +} From 6a7810b3a4d1050149681864d02d55ce722ed58f Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 29 Apr 2018 11:18:57 -0400 Subject: [PATCH 017/106] Fix Guild not being populated in SocketWebhookUser (#1044) --- src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 78a29639b..dd80648d2 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -26,6 +26,7 @@ namespace Discord.WebSocket internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) : base(guild.Discord, id) { + Guild = guild; WebhookId = webhookId; } internal static SocketWebhookUser Create(SocketGuild guild, ClientState state, Model model, ulong webhookId) From 660fec0cbc705b996838ba7a574fb36c02f35ce2 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sun, 29 Apr 2018 17:24:24 +0200 Subject: [PATCH 018/106] Expose the internal entity type readers (#986) * Expose the internal entity type readers * Add BestMatch property to TypeReaderResult for easily accessing the parsed object --- src/Discord.Net.Commands/Readers/ChannelTypeReader.cs | 4 ++-- src/Discord.Net.Commands/Readers/MessageTypeReader.cs | 4 ++-- src/Discord.Net.Commands/Readers/RoleTypeReader.cs | 4 ++-- src/Discord.Net.Commands/Readers/UserTypeReader.cs | 4 ++-- src/Discord.Net.Commands/Results/TypeReaderResult.cs | 6 +++++- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index 3136eb2cb..cd7a9d744 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord.Commands { - internal class ChannelTypeReader : TypeReader + public class ChannelTypeReader : TypeReader where T : class, IChannel { public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index fe576c3c6..a87cfbe43 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Globalization; using System.Threading.Tasks; namespace Discord.Commands { - internal class MessageTypeReader : TypeReader + public class MessageTypeReader : TypeReader where T : class, IMessage { public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 703374c05..508624103 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord.Commands { - internal class RoleTypeReader : TypeReader + public class RoleTypeReader : TypeReader where T : class, IRole { public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 8fc330d4c..425c2ccb7 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord.Commands { - internal class UserTypeReader : TypeReader + public class UserTypeReader : TypeReader where T : class, IUser { public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Results/TypeReaderResult.cs b/src/Discord.Net.Commands/Results/TypeReaderResult.cs index 68bc359c6..639ca3ac1 100644 --- a/src/Discord.Net.Commands/Results/TypeReaderResult.cs +++ b/src/Discord.Net.Commands/Results/TypeReaderResult.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; namespace Discord.Commands { @@ -30,6 +31,9 @@ namespace Discord.Commands public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; + public object BestMatch => IsSuccess + ? (Values.Count == 1 ? Values.Single().Value : Values.OrderByDescending(v => v.Score).First().Value) + : throw new InvalidOperationException("TypeReaderResult was not successful."); private TypeReaderResult(IReadOnlyCollection values, CommandError? error, string errorReason) { From e775853b1b26eaf80e6e76c295b44ca6241eaee7 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 4 May 2018 02:29:51 +0100 Subject: [PATCH 019/106] Expose VoiceServerUpdate events (#984) * Expose VoiceServerUpdate events * Amend based on feedback * Move this out of guild entity * Fix namespace issue * Adjust based on feedback #2 * Use cacheable instead * Change based on feedback --- .../BaseSocketClient.Events.cs | 9 +++++++- .../DiscordSocketClient.cs | 7 +++++++ .../SocketVoiceServer.cs | 21 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.WebSocket/SocketVoiceServer.cs diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index e881a7855..c236b1045 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.WebSocket @@ -165,6 +165,13 @@ namespace Discord.WebSocket remove { _userVoiceStateUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); + /// Fired when the bot connects to a Discord voice server. + public event Func VoiceServerUpdated + { + add { _voiceServerUpdatedEvent.Add(value); } + remove { _voiceServerUpdatedEvent.Remove(value); } + } + internal readonly AsyncEvent> _voiceServerUpdatedEvent = new AsyncEvent>(); /// Fired when the connected account is updated. public event Func CurrentUserUpdated { add { _selfUpdatedEvent.Add(value); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 18413e840..95925291b 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1466,6 +1466,12 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); var guild = State.GetGuild(data.GuildId); + var cacheable = new Cacheable(guild, data.GuildId, guild != null, + async () => await ApiClient.GetGuildAsync(data.GuildId).ConfigureAwait(false) as IGuild); + + var voiceServer = new SocketVoiceServer(cacheable, data.GuildId, data.Endpoint, data.Token); + await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); + if (guild != null) { string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); @@ -1476,6 +1482,7 @@ namespace Discord.WebSocket await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); return; } + } break; diff --git a/src/Discord.Net.WebSocket/SocketVoiceServer.cs b/src/Discord.Net.WebSocket/SocketVoiceServer.cs new file mode 100644 index 000000000..9ab6afd3f --- /dev/null +++ b/src/Discord.Net.WebSocket/SocketVoiceServer.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; + +namespace Discord.WebSocket +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class SocketVoiceServer + { + public Cacheable Guild { get; private set; } + public string Endpoint { get; private set; } + public string Token { get; private set; } + + internal SocketVoiceServer(Cacheable guild, ulong guildId, string endpoint, string token) + { + Guild = guild; + Endpoint = endpoint; + Token = token; + } + + private string DebuggerDisplay => $"SocketVoiceServer ({Guild.Id})"; + } +} From 7cfed7ff67ac9aee517de4130d9ccf504791ed61 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 3 May 2018 21:30:13 -0400 Subject: [PATCH 020/106] Fix nullref when passing null to GetShardIdFor. (#1049) --- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 45d76e61a..dad53b3b7 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -129,7 +129,7 @@ namespace Discord.WebSocket private int GetShardIdFor(ulong guildId) => (int)((guildId >> 22) % (uint)_totalShards); public int GetShardIdFor(IGuild guild) - => GetShardIdFor(guild.Id); + => GetShardIdFor(guild?.Id ?? 0); private DiscordSocketClient GetShardFor(ulong guildId) => GetShard(GetShardIdFor(guildId)); public DiscordSocketClient GetShardFor(IGuild guild) From bb4bb138460cc1e6bac53b4e838893dcfd812ce7 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Fri, 4 May 2018 11:42:54 +0100 Subject: [PATCH 021/106] Fix issues with #984, remove extraneous whitespace (#1051) - Removed unnecessary parameter in SocketVoiceServer - Moved SocketVoiceServer into Entities/Voice - Fixed a bug where trying to download the cached guild would throw - Fixed a potential bug where Discord might not give us a port when connecting to voice --- .../DiscordSocketClient.cs | 72 ++++++++++--------- .../{ => Entities/Voice}/SocketVoiceServer.cs | 2 +- 2 files changed, 40 insertions(+), 34 deletions(-) rename src/Discord.Net.WebSocket/{ => Entities/Voice}/SocketVoiceServer.cs (92%) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 95925291b..72b0b022b 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -62,9 +62,9 @@ namespace Discord.WebSocket internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; public override IReadOnlyCollection Guilds => State.Guilds; public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; - public IReadOnlyCollection DMChannels + public IReadOnlyCollection DMChannels => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); - public IReadOnlyCollection GroupChannels + public IReadOnlyCollection GroupChannels => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); @@ -89,11 +89,11 @@ namespace Discord.WebSocket _stateLock = new SemaphoreSlim(1, 1); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); - _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, + _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); _connection.Disconnected += (ex, recon) => TimedInvokeAsync(_disconnectedEvent, nameof(Disconnected), ex); - + _nextAudioId = 1; _connectionGroupLock = groupLock; _parentClient = parentClient; @@ -104,7 +104,7 @@ namespace Discord.WebSocket _gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult(); e.ErrorContext.Handled = true; }; - + ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; @@ -136,7 +136,7 @@ namespace Discord.WebSocket ApiClient.Dispose(); } } - + internal override async Task OnLoginAsync(TokenType tokenType, string token) { if (_parentClient == null) @@ -154,11 +154,11 @@ namespace Discord.WebSocket _voiceRegions = ImmutableDictionary.Create(); } - public override async Task StartAsync() + public override async Task StartAsync() => await _connection.StartAsync().ConfigureAwait(false); - public override async Task StopAsync() + public override async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); - + private async Task OnConnectingAsync() { if (_connectionGroupLock != null) @@ -181,11 +181,11 @@ namespace Discord.WebSocket //Wait for READY await _connection.WaitAsync().ConfigureAwait(false); - + await _gatewayLogger.DebugAsync("Sending Status").ConfigureAwait(false); await SendStatusAsync().ConfigureAwait(false); } - finally + finally { if (_connectionGroupLock != null) { @@ -230,22 +230,22 @@ namespace Discord.WebSocket } /// - public override async Task GetApplicationInfoAsync(RequestOptions options = null) + public override async Task GetApplicationInfoAsync(RequestOptions options = null) => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); /// - public override SocketGuild GetGuild(ulong id) - => State.GetGuild(id); + public override SocketGuild GetGuild(ulong id) + => State.GetGuild(id); /// - public override SocketChannel GetChannel(ulong id) + public override SocketChannel GetChannel(ulong id) => State.GetChannel(id); - + /// - public override SocketUser GetUser(ulong id) + public override SocketUser GetUser(ulong id) => State.GetUser(id); /// - public override SocketUser GetUser(string username, string discriminator) + public override SocketUser GetUser(string username, string discriminator) => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) { @@ -266,7 +266,7 @@ namespace Discord.WebSocket return user; }); } - internal void RemoveUser(ulong id) + internal void RemoveUser(ulong id) => State.RemoveUser(id); /// @@ -340,7 +340,7 @@ namespace Discord.WebSocket Activity = activity; await SendStatusAsync().ConfigureAwait(false); } - + private async Task SendStatusAsync() { if (CurrentUser == null) @@ -374,7 +374,7 @@ namespace Discord.WebSocket if (seq != null) _lastSeq = seq.Value; _lastMessageTime = Environment.TickCount; - + try { switch (opCode) @@ -390,7 +390,7 @@ namespace Discord.WebSocket case GatewayOpCode.Heartbeat: { await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); - + await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); } break; @@ -415,7 +415,7 @@ namespace Discord.WebSocket _sessionId = null; _lastSeq = 0; - + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false); } break; @@ -475,7 +475,7 @@ namespace Discord.WebSocket } else if (_connection.CancelToken.IsCancellationRequested) return; - + await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); }); @@ -514,7 +514,7 @@ namespace Discord.WebSocket if (guild != null) { guild.Update(State, data); - + if (_unavailableGuildCount != 0) _unavailableGuildCount--; await GuildAvailableAsync(guild).ConfigureAwait(false); @@ -1025,7 +1025,7 @@ namespace Discord.WebSocket SocketUser user = guild.GetUser(data.User.Id); if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); + user = SocketUnknownUser.Create(this, State, data.User); await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); } else @@ -1325,7 +1325,7 @@ namespace Discord.WebSocket await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); } } - + var before = user.Clone(); user.Update(State, data, true); await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); @@ -1466,21 +1466,27 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); var guild = State.GetGuild(data.GuildId); - var cacheable = new Cacheable(guild, data.GuildId, guild != null, - async () => await ApiClient.GetGuildAsync(data.GuildId).ConfigureAwait(false) as IGuild); + var isCached = guild != null; + var cachedGuild = new Cacheable(guild, data.GuildId, isCached, + () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); - var voiceServer = new SocketVoiceServer(cacheable, data.GuildId, data.Endpoint, data.Token); + var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); - if (guild != null) + if (isCached) { - string endpoint = data.Endpoint.Substring(0, data.Endpoint.LastIndexOf(':')); + var endpoint = data.Endpoint; + + //Only strip out the port if the endpoint contains it + var portBegin = endpoint.LastIndexOf(':'); + if (portBegin > 0) + endpoint = endpoint.Substring(0, portBegin); + var _ = guild.FinishConnectAudio(endpoint, data.Token).ConfigureAwait(false); } else { await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; } } diff --git a/src/Discord.Net.WebSocket/SocketVoiceServer.cs b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs similarity index 92% rename from src/Discord.Net.WebSocket/SocketVoiceServer.cs rename to src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs index 9ab6afd3f..57abf1d03 100644 --- a/src/Discord.Net.WebSocket/SocketVoiceServer.cs +++ b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs @@ -9,7 +9,7 @@ namespace Discord.WebSocket public string Endpoint { get; private set; } public string Token { get; private set; } - internal SocketVoiceServer(Cacheable guild, ulong guildId, string endpoint, string token) + internal SocketVoiceServer(Cacheable guild, string endpoint, string token) { Guild = guild; Endpoint = endpoint; From 97c893107b51de24a38ea03c2f8260030c8fc7f5 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 7 May 2018 06:22:49 +0800 Subject: [PATCH 022/106] Implement GetBanAsync (#1056) --- .../Entities/Guilds/IGuild.cs | 22 +++++++++++++++++-- src/Discord.Net.Rest/DiscordRestApiClient.cs | 9 ++++++++ .../Entities/Guilds/GuildHelper.cs | 7 +++++- .../Entities/Guilds/RestGuild.cs | 12 +++++++++- .../Entities/Guilds/SocketGuild.cs | 10 +++++++++ 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 2f0599d76..1761b3f88 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -66,6 +66,24 @@ namespace Discord /// Gets a collection of all users banned on this guild. Task> GetBansAsync(RequestOptions options = null); + /// + /// Gets a ban object for a banned user. + /// + /// The banned user. + /// + /// An awaitable containing the ban object, which contains the user information and the + /// reason for the ban; if the ban entry cannot be found. + /// + Task GetBanAsync(IUser user, RequestOptions options = null); + /// + /// Gets a ban object for a banned user. + /// + /// The snowflake identifier for the banned user. + /// + /// An awaitable containing the ban object, which contains the user information and the + /// reason for the ban; if the ban entry cannot be found. + /// + Task GetBanAsync(ulong userId, RequestOptions options = null); /// Bans the provided user from this guild and optionally prunes their recent messages. /// The number of days to remove messages from this user for - must be between [0, 7] Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); @@ -135,4 +153,4 @@ namespace Discord /// Deletes an existing emote from this guild. Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 042ae9970..fbf9896b8 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -800,6 +800,15 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); } + public async Task GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options) + { + Preconditions.NotEqual(userId, 0, nameof(userId)); + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); + } public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 12fdb075d..e3fd1a8c3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -1,4 +1,4 @@ -using Discord.API.Rest; +using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -111,6 +111,11 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } + public static async Task GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) + { + var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); + return RestBan.Create(client, model); + } public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, int pruneDays, string reason, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 5d12731a6..a7123930a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -140,6 +140,10 @@ namespace Discord.Rest //Bans public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + public Task GetBanAsync(ulong userId, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, userId, options); public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); @@ -291,6 +295,12 @@ namespace Discord.Rest async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 259dae5a8..3dbd1160c 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -288,6 +288,10 @@ namespace Discord.WebSocket //Bans public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + public Task GetBanAsync(ulong userId, RequestOptions options = null) + => GuildHelper.GetBanAsync(this, Discord, userId, options); public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); @@ -641,6 +645,12 @@ namespace Discord.WebSocket async Task> IGuild.GetBansAsync(RequestOptions options) => await GetBansAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Channels); From 39dffe858584d0e9eed750bff6426e9562db4262 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sun, 13 May 2018 01:46:07 +0100 Subject: [PATCH 023/106] Audit Logs implementation (#1055) * Copy audit logs impl from old branch and clean up I suck at using git, so I'm gonna use brute force. * Remove unnecessary TODOs Category channels do not provide any new information, and the other I forgot to remove beforehand * Add invite update data, clean up after feedback * Remove TODOs, add WebhookType enum for future use WebhookType is a future-use type, as currently audit logs are the only thing which may return it. --- src/Discord.Net.Core/DiscordConfig.cs | 7 +- .../Entities/AuditLogs/ActionType.cs | 50 ++++++++++++ .../Entities/AuditLogs/IAuditLogData.cs | 14 ++++ .../Entities/AuditLogs/IAuditLogEntry.cs | 34 ++++++++ .../Entities/Guilds/IGuild.cs | 4 + .../Entities/Webhooks/WebhookType.cs | 14 ++++ src/Discord.Net.Rest/API/Common/AuditLog.cs | 16 ++++ .../API/Common/AuditLogChange.cs | 17 ++++ .../API/Common/AuditLogEntry.cs | 26 ++++++ .../API/Common/AuditLogOptions.cs | 27 +++++++ .../API/Rest/GetAuditLogsParams.cs | 8 ++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 20 +++++ .../Entities/AuditLogs/AuditLogHelper.cs | 58 ++++++++++++++ .../AuditLogs/DataTypes/BanAuditLogData.cs | 23 ++++++ .../DataTypes/ChannelCreateAuditLogData.cs | 52 ++++++++++++ .../DataTypes/ChannelDeleteAuditLogData.cs | 45 +++++++++++ .../AuditLogs/DataTypes/ChannelInfo.cs | 18 +++++ .../DataTypes/ChannelUpdateAuditLogData.cs | 45 +++++++++++ .../DataTypes/EmoteCreateAuditLogData.cs | 31 ++++++++ .../DataTypes/EmoteDeleteAuditLogData.cs | 28 +++++++ .../DataTypes/EmoteUpdateAuditLogData.cs | 31 ++++++++ .../Entities/AuditLogs/DataTypes/GuildInfo.cs | 32 ++++++++ .../DataTypes/GuildUpdateAuditLogData.cs | 79 +++++++++++++++++++ .../DataTypes/InviteCreateAuditLogData.cs | 55 +++++++++++++ .../DataTypes/InviteDeleteAuditLogData.cs | 55 +++++++++++++ .../AuditLogs/DataTypes/InviteInfo.cs | 20 +++++ .../DataTypes/InviteUpdateAuditLogData.cs | 46 +++++++++++ .../AuditLogs/DataTypes/KickAuditLogData.cs | 23 ++++++ .../DataTypes/MemberRoleAuditLogData.cs | 50 ++++++++++++ .../DataTypes/MemberUpdateAuditLogData.cs | 35 ++++++++ .../DataTypes/MessageDeleteAuditLogData.cs | 22 ++++++ .../DataTypes/OverwriteCreateAuditLogData.cs | 37 +++++++++ .../DataTypes/OverwriteDeleteAuditLogData.cs | 42 ++++++++++ .../DataTypes/OverwriteUpdateAuditLogData.cs | 44 +++++++++++ .../AuditLogs/DataTypes/PruneAuditLogData.cs | 22 ++++++ .../DataTypes/RoleCreateAuditLogData.cs | 47 +++++++++++ .../DataTypes/RoleDeleteAuditLogData.cs | 47 +++++++++++ .../Entities/AuditLogs/DataTypes/RoleInfo.cs | 21 +++++ .../DataTypes/RoleUpdateAuditLogData.cs | 62 +++++++++++++++ .../AuditLogs/DataTypes/UnbanAuditLogData.cs | 23 ++++++ .../DataTypes/WebhookCreateAuditLogData.cs | 44 +++++++++++ .../DataTypes/WebhookDeleteAuditLogData.cs | 46 +++++++++++ .../AuditLogs/DataTypes/WebhookInfo.cs | 16 ++++ .../DataTypes/WebhookUpdateAuditLogData.cs | 52 ++++++++++++ .../Entities/AuditLogs/RestAuditLogEntry.cs | 38 +++++++++ .../Entities/Guilds/GuildHelper.cs | 29 +++++++ .../Entities/Guilds/RestGuild.cs | 12 +++ .../Entities/Guilds/SocketGuild.cs | 12 +++ 48 files changed, 1576 insertions(+), 3 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs create mode 100644 src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs create mode 100644 src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLog.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogChange.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogEntry.cs create mode 100644 src/Discord.Net.Rest/API/Common/AuditLogOptions.cs create mode 100644 src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index fd2fe92e8..1db3271e5 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -4,10 +4,10 @@ namespace Discord { public class DiscordConfig { - public const int APIVersion = 6; + public const int APIVersion = 6; public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? - typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? + typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? "Unknown"; public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; @@ -20,10 +20,11 @@ namespace Discord public const int MaxMessagesPerBatch = 100; public const int MaxUsersPerBatch = 1000; public const int MaxGuildsPerBatch = 100; + public const int MaxAuditLogEntriesPerBatch = 100; /// Gets or sets how a request should act in the case of an error, by default. public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; - + /// Gets or sets the minimum log level severity that will be sent to the Log event. public LogSeverity LogLevel { get; set; } = LogSeverity.Info; diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs new file mode 100644 index 000000000..e5a4ff30a --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// The action type within a + /// + public enum ActionType + { + GuildUpdated = 1, + + ChannelCreated = 10, + ChannelUpdated = 11, + ChannelDeleted = 12, + + OverwriteCreated = 13, + OverwriteUpdated = 14, + OverwriteDeleted = 15, + + Kick = 20, + Prune = 21, + Ban = 22, + Unban = 23, + + MemberUpdated = 24, + MemberRoleUpdated = 25, + + RoleCreated = 30, + RoleUpdated = 31, + RoleDeleted = 32, + + InviteCreated = 40, + InviteUpdated = 41, + InviteDeleted = 42, + + WebhookCreated = 50, + WebhookUpdated = 51, + WebhookDeleted = 52, + + EmojiCreated = 60, + EmojiUpdated = 61, + EmojiDeleted = 62, + + MessageDeleted = 72 + } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs new file mode 100644 index 000000000..47aaffb26 --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents data applied to an + /// + public interface IAuditLogData + { } +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs new file mode 100644 index 000000000..b85730a1d --- /dev/null +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents an entry in an audit log + /// + public interface IAuditLogEntry : IEntity + { + /// + /// The action which occured to create this entry + /// + ActionType Action { get; } + + /// + /// The data for this entry. May be if no data was available. + /// + IAuditLogData Data { get; } + + /// + /// The user responsible for causing the changes + /// + IUser User { get; } + + /// + /// The reason behind the change. May be if no reason was provided. + /// + string Reason { get; } + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 1761b3f88..19d2ee81c 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -139,6 +139,10 @@ namespace Discord /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); + /// Gets the specified number of audit log entries for this guild. + Task> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, + CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Gets the webhook in this guild with the provided id, or null if not found. Task GetWebhookAsync(ulong id, RequestOptions options = null); /// Gets a collection of all webhooks for this guild. diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs new file mode 100644 index 000000000..0ddfa393d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + /// + /// Represents the type of a webhook. + /// + /// + /// This type is currently unused, and is only returned in audit log responses. + /// + public enum WebhookType + { + /// An incoming webhook + Incoming = 1 + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs new file mode 100644 index 000000000..cd8ad147d --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLog + { + [JsonProperty("webhooks")] + public Webhook[] Webhooks { get; set; } + + [JsonProperty("users")] + public User[] Users { get; set; } + + [JsonProperty("audit_log_entries")] + public AuditLogEntry[] Entries { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogChange.cs b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs new file mode 100644 index 000000000..44e585021 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Discord.API +{ + internal class AuditLogChange + { + [JsonProperty("key")] + public string ChangedProperty { get; set; } + + [JsonProperty("new_value")] + public JToken NewValue { get; set; } + + [JsonProperty("old_value")] + public JToken OldValue { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs new file mode 100644 index 000000000..80d9a9e97 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogEntry + { + [JsonProperty("target_id")] + public ulong? TargetId { get; set; } + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("changes")] + public AuditLogChange[] Changes { get; set; } + [JsonProperty("options")] + public AuditLogOptions Options { get; set; } + + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("action_type")] + public ActionType Action { get; set; } + + [JsonProperty("reason")] + public string Reason { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs new file mode 100644 index 000000000..65b401cce --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class AuditLogOptions + { + //Message delete + [JsonProperty("count")] + public int? MessageDeleteCount { get; set; } + [JsonProperty("channel_id")] + public ulong? MessageDeleteChannelId { get; set; } + + //Prune + [JsonProperty("delete_member_days")] + public int? PruneDeleteMemberDays { get; set; } + [JsonProperty("members_removed")] + public int? PruneMembersRemoved { get; set; } + + //Overwrite Update + [JsonProperty("role_name")] + public string OverwriteRoleName { get; set; } + [JsonProperty("type")] + public string OverwriteType { get; set; } + [JsonProperty("id")] + public ulong? OverwriteTargetId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs new file mode 100644 index 000000000..ceabccbc8 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs @@ -0,0 +1,8 @@ +namespace Discord.API.Rest +{ + class GetAuditLogsParams + { + public Optional Limit { get; set; } + public Optional BeforeEntryId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index fbf9896b8..8b641fb6a 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1206,6 +1206,26 @@ namespace Discord.API return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); } + //Audit logs + public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + options = RequestOptions.CreateOrClone(options); + + int limit = args.Limit.GetValueOrDefault(int.MaxValue); + + var ids = new BucketIds(guildId: guildId); + Expression> endpoint; + + if (args.BeforeEntryId.IsSpecified) + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}&before={args.BeforeEntryId.Value}"; + else + endpoint = () => $"guilds/{guildId}/audit-logs?limit={limit}"; + + return await SendAsync("GET", endpoint, ids, options: options).ConfigureAwait(false); + } + //Webhooks public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs new file mode 100644 index 000000000..7936343f3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + internal static class AuditLogHelper + { + private static readonly Dictionary> CreateMapping + = new Dictionary>() + { + [ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create, + + [ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create, + [ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create, + [ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create, + + [ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create, + [ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create, + [ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create, + + [ActionType.Kick] = KickAuditLogData.Create, + [ActionType.Prune] = PruneAuditLogData.Create, + [ActionType.Ban] = BanAuditLogData.Create, + [ActionType.Unban] = UnbanAuditLogData.Create, + [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, + [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, + + [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, + [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, + [ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create, + + [ActionType.InviteCreated] = InviteCreateAuditLogData.Create, + [ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create, + [ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create, + + [ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create, + [ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create, + [ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create, + + [ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create, + [ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create, + [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, + + [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create, + }; + + public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) + { + if (CreateMapping.TryGetValue(entry.Action, out var func)) + return func(discord, log, entry); + + return null; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs new file mode 100644 index 000000000..4b9d5875f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class BanAuditLogData : IAuditLogData + { + private BanAuditLogData(IUser user) + { + Target = user; + } + + internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new BanAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs new file mode 100644 index 000000000..ef4787295 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelCreateAuditLogData : IAuditLogData + { + private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + var overwrites = new List(); + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var type = typeModel.NewValue.ToObject(); + var name = nameModel.NewValue.ToObject(); + + foreach (var overwrite in overwritesModel.NewValue) + { + var deny = overwrite.Value("deny"); + var _type = overwrite.Value("type"); + var id = overwrite.Value("id"); + var allow = overwrite.Value("allow"); + + PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); + } + + return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs new file mode 100644 index 000000000..4816ce770 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelDeleteAuditLogData : IAuditLogData + { + private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + { + ChannelId = id; + ChannelName = name; + ChannelType = type; + Overwrites = overwrites; + } + + internal static ChannelDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var overwrites = overwritesModel.OldValue.ToObject() + .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) + .ToList(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var id = entry.TargetId.Value; + + return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); + } + + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs new file mode 100644 index 000000000..e2d6064a9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelInfo.cs @@ -0,0 +1,18 @@ +namespace Discord.Rest +{ + public struct ChannelInfo + { + internal ChannelInfo(string name, string topic, int? bitrate, int? limit) + { + Name = name; + Topic = topic; + Bitrate = bitrate; + UserLimit = limit; + } + + public string Name { get; } + public string Topic { get; } + public int? Bitrate { get; } + public int? UserLimit { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs new file mode 100644 index 000000000..f3403138d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -0,0 +1,45 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class ChannelUpdateAuditLogData : IAuditLogData + { + private ChannelUpdateAuditLogData(ulong id, ChannelInfo before, ChannelInfo after) + { + ChannelId = id; + Before = before; + After = after; + } + + internal static ChannelUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var topicModel = changes.FirstOrDefault(x => x.ChangedProperty == "topic"); + var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); + var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); + + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + string oldTopic = topicModel?.OldValue?.ToObject(), + newTopic = topicModel?.NewValue?.ToObject(); + int? oldBitrate = bitrateModel?.OldValue?.ToObject(), + newBitrate = bitrateModel?.NewValue?.ToObject(); + int? oldLimit = userLimitModel?.OldValue?.ToObject(), + newLimit = userLimitModel?.NewValue?.ToObject(); + + var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit); + var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit); + + return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); + } + + public ulong ChannelId { get; } + public ChannelInfo Before { get; set; } + public ChannelInfo After { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs new file mode 100644 index 000000000..5d1ef8463 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteCreateAuditLogData : IAuditLogData + { + private EmoteCreateAuditLogData(ulong id, string name) + { + EmoteId = id; + Name = name; + } + + internal static EmoteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var emoteName = change.NewValue?.ToObject(); + return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); + } + + public ulong EmoteId { get; } + public string Name { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs new file mode 100644 index 000000000..d0a11191f --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -0,0 +1,28 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteDeleteAuditLogData : IAuditLogData + { + private EmoteDeleteAuditLogData(ulong id, string name) + { + EmoteId = id; + Name = name; + } + + internal static EmoteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var emoteName = change.OldValue?.ToObject(); + + return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); + } + + public ulong EmoteId { get; } + public string Name { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs new file mode 100644 index 000000000..60020bcaa --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -0,0 +1,31 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class EmoteUpdateAuditLogData : IAuditLogData + { + private EmoteUpdateAuditLogData(ulong id, string oldName, string newName) + { + EmoteId = id; + OldName = oldName; + NewName = newName; + } + + internal static EmoteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var newName = change.NewValue?.ToObject(); + var oldName = change.OldValue?.ToObject(); + + return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); + } + + public ulong EmoteId { get; } + public string NewName { get; } + public string OldName { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs new file mode 100644 index 000000000..90865ef72 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildInfo.cs @@ -0,0 +1,32 @@ +namespace Discord.Rest +{ + public struct GuildInfo + { + internal GuildInfo(int? afkTimeout, DefaultMessageNotifications? defaultNotifs, + ulong? afkChannel, string name, string region, string icon, + VerificationLevel? verification, IUser owner, MfaLevel? mfa, int? filter) + { + AfkTimeout = afkTimeout; + DefaultMessageNotifications = defaultNotifs; + AfkChannelId = afkChannel; + Name = name; + RegionId = region; + IconHash = icon; + VerificationLevel = verification; + Owner = owner; + MfaLevel = mfa; + ContentFilterLevel = filter; + } + + public int? AfkTimeout { get; } + public DefaultMessageNotifications? DefaultMessageNotifications { get; } + public ulong? AfkChannelId { get; } + public string Name { get; } + public string RegionId { get; } + public string IconHash { get; } + public VerificationLevel? VerificationLevel { get; } + public IUser Owner { get; } + public MfaLevel? MfaLevel { get; } + public int? ContentFilterLevel { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs new file mode 100644 index 000000000..08550ed7a --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -0,0 +1,79 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class GuildUpdateAuditLogData : IAuditLogData + { + private GuildUpdateAuditLogData(GuildInfo before, GuildInfo after) + { + Before = before; + After = after; + } + + internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var afkTimeoutModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var defaultMessageNotificationsModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var afkChannelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var regionIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var iconHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var verificationLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var ownerIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); + + int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(), + newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(); + DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(), + newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(); + ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(), + newAfkChannelId = afkChannelModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + string oldRegionId = regionIdModel?.OldValue?.ToObject(), + newRegionId = regionIdModel?.NewValue?.ToObject(); + string oldIconHash = iconHashModel?.OldValue?.ToObject(), + newIconHash = iconHashModel?.NewValue?.ToObject(); + VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(), + newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(); + ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(), + newOwnerId = ownerIdModel?.NewValue?.ToObject(); + MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(), + newMfaLevel = mfaLevelModel?.NewValue?.ToObject(); + int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(), + newContentFilter = contentFilterModel?.NewValue?.ToObject(); + + IUser oldOwner = null; + if (oldOwnerId != null) + { + var oldOwnerInfo = log.Users.FirstOrDefault(x => x.Id == oldOwnerId.Value); + oldOwner = RestUser.Create(discord, oldOwnerInfo); + } + + IUser newOwner = null; + if (newOwnerId != null) + { + var newOwnerInfo = log.Users.FirstOrDefault(x => x.Id == newOwnerId.Value); + newOwner = RestUser.Create(discord, newOwnerInfo); + } + + var before = new GuildInfo(oldAfkTimeout, oldDefaultMessageNotifications, + oldAfkChannelId, oldName, oldRegionId, oldIconHash, oldVerificationLevel, oldOwner, + oldMfaLevel, oldContentFilter); + var after = new GuildInfo(newAfkTimeout, newDefaultMessageNotifications, + newAfkChannelId, newName, newRegionId, newIconHash, newVerificationLevel, newOwner, + newMfaLevel, newContentFilter); + + return new GuildUpdateAuditLogData(before, after); + } + + public GuildInfo Before { get; } + public GuildInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs new file mode 100644 index 000000000..292715420 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -0,0 +1,55 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteCreateAuditLogData : IAuditLogData + { + private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.NewValue.ToObject(); + var code = codeModel.NewValue.ToObject(); + var temporary = temporaryModel.NewValue.ToObject(); + var inviterId = inviterIdModel.NewValue.ToObject(); + var channelId = channelIdModel.NewValue.ToObject(); + var uses = usesModel.NewValue.ToObject(); + var maxUses = maxUsesModel.NewValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + public int MaxUses { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs new file mode 100644 index 000000000..1dc6d518b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -0,0 +1,55 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteDeleteAuditLogData : IAuditLogData + { + private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + Creator = inviter; + ChannelId = channelId; + Uses = uses; + MaxUses = maxUses; + } + + internal static InviteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var inviterIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "inviter_id"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + var maxAge = maxAgeModel.OldValue.ToObject(); + var code = codeModel.OldValue.ToObject(); + var temporary = temporaryModel.OldValue.ToObject(); + var inviterId = inviterIdModel.OldValue.ToObject(); + var channelId = channelIdModel.OldValue.ToObject(); + var uses = usesModel.OldValue.ToObject(); + var maxUses = maxUsesModel.OldValue.ToObject(); + + var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); + var inviter = RestUser.Create(discord, inviterInfo); + + return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); + } + + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + public int MaxUses { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs new file mode 100644 index 000000000..c9840f6cc --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteInfo.cs @@ -0,0 +1,20 @@ +namespace Discord.Rest +{ + public struct InviteInfo + { + internal InviteInfo(int? maxAge, string code, bool? temporary, ulong? channelId, int? maxUses) + { + MaxAge = maxAge; + Code = code; + Temporary = temporary; + ChannelId = channelId; + MaxUses = maxUses; + } + + public int? MaxAge { get; } + public string Code { get; } + public bool? Temporary { get; } + public ulong? ChannelId { get; } + public int? MaxUses { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs new file mode 100644 index 000000000..b932cfbfc --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -0,0 +1,46 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class InviteUpdateAuditLogData : IAuditLogData + { + private InviteUpdateAuditLogData(InviteInfo before, InviteInfo after) + { + Before = before; + After = after; + } + + internal static InviteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var maxAgeModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_age"); + var codeModel = changes.FirstOrDefault(x => x.ChangedProperty == "code"); + var temporaryModel = changes.FirstOrDefault(x => x.ChangedProperty == "temporary"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); + + int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(), + newMaxAge = maxAgeModel?.NewValue?.ToObject(); + string oldCode = codeModel?.OldValue?.ToObject(), + newCode = codeModel?.NewValue?.ToObject(); + bool? oldTemporary = temporaryModel?.OldValue?.ToObject(), + newTemporary = temporaryModel?.NewValue?.ToObject(); + ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(), + newChannelId = channelIdModel?.NewValue?.ToObject(); + int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(), + newMaxUses = maxUsesModel?.NewValue?.ToObject(); + + var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses); + var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses); + + return new InviteUpdateAuditLogData(before, after); + } + + public InviteInfo Before { get; } + public InviteInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs new file mode 100644 index 000000000..41b5526b8 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class KickAuditLogData : IAuditLogData + { + private KickAuditLogData(RestUser user) + { + Target = user; + } + + internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new KickAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs new file mode 100644 index 000000000..b0f0a1fe1 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class MemberRoleAuditLogData : IAuditLogData + { + private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) + { + Roles = roles; + Target = target; + } + + internal static MemberRoleAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), + (model, role) => new { model.ChangedProperty, Role = role }) + .Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) + .ToList(); + + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, userInfo); + + return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); + } + + public IReadOnlyCollection Roles { get; } + public IUser Target { get; } + + public struct RoleInfo + { + internal RoleInfo(string name, ulong roleId, bool added) + { + Name = name; + RoleId = roleId; + Added = added; + } + + public string Name { get; } + public ulong RoleId { get; } + public bool Added { get; } + } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs new file mode 100644 index 000000000..38f078848 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -0,0 +1,35 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; + +namespace Discord.Rest +{ + public class MemberUpdateAuditLogData : IAuditLogData + { + private MemberUpdateAuditLogData(IUser target, string newNick, string oldNick) + { + Target = target; + NewNick = newNick; + OldNick = oldNick; + } + + internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick"); + + var newNick = changes.NewValue?.ToObject(); + var oldNick = changes.OldValue?.ToObject(); + + var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + var user = RestUser.Create(discord, targetInfo); + + return new MemberUpdateAuditLogData(user, newNick, oldNick); + } + + public IUser Target { get; } + public string NewNick { get; } + public string OldNick { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs new file mode 100644 index 000000000..3949cdd68 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class MessageDeleteAuditLogData : IAuditLogData + { + private MessageDeleteAuditLogData(ulong channelId, int count) + { + ChannelId = channelId; + MessageCount = count; + } + + internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value); + } + + public int MessageCount { get; } + public ulong ChannelId { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs new file mode 100644 index 000000000..d58488136 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -0,0 +1,37 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteCreateAuditLogData : IAuditLogData + { + private OverwriteCreateAuditLogData(Overwrite overwrite) + { + Overwrite = overwrite; + } + + internal static OverwriteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.NewValue.ToObject(); + var allow = allowModel.NewValue.ToObject(); + + var permissions = new OverwritePermissions(allow, deny); + + var id = entry.Options.OverwriteTargetId.Value; + var type = entry.Options.OverwriteType; + + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); + } + + public Overwrite Overwrite { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs new file mode 100644 index 000000000..445c2e302 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; +using ChangeModel = Discord.API.AuditLogChange; +using OptionModel = Discord.API.AuditLogOptions; + +namespace Discord.Rest +{ + public class OverwriteDeleteAuditLogData : IAuditLogData + { + private OverwriteDeleteAuditLogData(Overwrite deletedOverwrite) + { + Overwrite = deletedOverwrite; + } + + internal static OverwriteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var deny = denyModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); + var id = idModel.OldValue.ToObject(); + var allow = allowModel.OldValue.ToObject(); + + PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + } + + public Overwrite Overwrite { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs new file mode 100644 index 000000000..d000146c3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -0,0 +1,44 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class OverwriteUpdateAuditLogData : IAuditLogData + { + private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType) + { + OldPermissions = before; + NewPermissions = after; + OverwriteTargetId = targetId; + OverwriteType = targetType; + } + + internal static OverwriteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); + var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); + + var beforeAllow = allowModel?.OldValue?.ToObject(); + var afterAllow = allowModel?.NewValue?.ToObject(); + var beforeDeny = denyModel?.OldValue?.ToObject(); + var afterDeny = denyModel?.OldValue?.ToObject(); + + var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); + var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); + + PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; + + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + } + + public OverwritePermissions OldPermissions { get; } + public OverwritePermissions NewPermissions { get; } + + public ulong OverwriteTargetId { get; } + public PermissionTarget OverwriteType { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs new file mode 100644 index 000000000..0005e304d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs @@ -0,0 +1,22 @@ +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class PruneAuditLogData : IAuditLogData + { + private PruneAuditLogData(int pruneDays, int membersRemoved) + { + PruneDays = pruneDays; + MembersRemoved = membersRemoved; + } + + internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); + } + + public int PruneDays { get; } + public int MembersRemoved { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs new file mode 100644 index 000000000..aa951d6e7 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -0,0 +1,47 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleCreateAuditLogData : IAuditLogData + { + private RoleCreateAuditLogData(ulong id, RoleInfo props) + { + RoleId = id; + Properties = props; + } + + internal static RoleCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? colorRaw = colorModel?.NewValue?.ToObject(); + bool? mentionable = mentionableModel?.NewValue?.ToObject(); + bool? hoist = hoistModel?.NewValue?.ToObject(); + string name = nameModel?.NewValue?.ToObject(); + ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(); + + Color? color = null; + GuildPermissions? permissions = null; + + if (colorRaw.HasValue) + color = new Color(colorRaw.Value); + if (permissionsRaw.HasValue) + permissions = new GuildPermissions(permissionsRaw.Value); + + return new RoleCreateAuditLogData(entry.TargetId.Value, + new RoleInfo(color, mentionable, hoist, name, permissions)); + } + + public ulong RoleId { get; } + public RoleInfo Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs new file mode 100644 index 000000000..e90d70d4d --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -0,0 +1,47 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleDeleteAuditLogData : IAuditLogData + { + private RoleDeleteAuditLogData(ulong id, RoleInfo props) + { + RoleId = id; + Properties = props; + } + + internal static RoleDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? colorRaw = colorModel?.OldValue?.ToObject(); + bool? mentionable = mentionableModel?.OldValue?.ToObject(); + bool? hoist = hoistModel?.OldValue?.ToObject(); + string name = nameModel?.OldValue?.ToObject(); + ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(); + + Color? color = null; + GuildPermissions? permissions = null; + + if (colorRaw.HasValue) + color = new Color(colorRaw.Value); + if (permissionsRaw.HasValue) + permissions = new GuildPermissions(permissionsRaw.Value); + + return new RoleDeleteAuditLogData(entry.TargetId.Value, + new RoleInfo(color, mentionable, hoist, name, permissions)); + } + + public ulong RoleId { get; } + public RoleInfo Properties { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs new file mode 100644 index 000000000..2208990e6 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs @@ -0,0 +1,21 @@ +namespace Discord.Rest +{ + public struct RoleInfo + { + internal RoleInfo(Color? color, bool? mentionable, bool? hoist, string name, + GuildPermissions? permissions) + { + Color = color; + Mentionable = mentionable; + Hoist = hoist; + Name = name; + Permissions = permissions; + } + + public Color? Color { get; } + public bool? Mentionable { get; } + public bool? Hoist { get; } + public string Name { get; } + public GuildPermissions? Permissions { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs new file mode 100644 index 000000000..be484e2d5 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -0,0 +1,62 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RoleUpdateAuditLogData : IAuditLogData + { + private RoleUpdateAuditLogData(ulong id, RoleInfo oldProps, RoleInfo newProps) + { + RoleId = id; + Before = oldProps; + After = newProps; + } + + internal static RoleUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var colorModel = changes.FirstOrDefault(x => x.ChangedProperty == "color"); + var mentionableModel = changes.FirstOrDefault(x => x.ChangedProperty == "mentionable"); + var hoistModel = changes.FirstOrDefault(x => x.ChangedProperty == "hoist"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); + + uint? oldColorRaw = colorModel?.OldValue?.ToObject(), + newColorRaw = colorModel?.NewValue?.ToObject(); + bool? oldMentionable = mentionableModel?.OldValue?.ToObject(), + newMentionable = mentionableModel?.NewValue?.ToObject(); + bool? oldHoist = hoistModel?.OldValue?.ToObject(), + newHoist = hoistModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(), + newName = nameModel?.NewValue?.ToObject(); + ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(), + newPermissionsRaw = permissionsModel?.OldValue?.ToObject(); + + Color? oldColor = null, + newColor = null; + GuildPermissions? oldPermissions = null, + newPermissions = null; + + if (oldColorRaw.HasValue) + oldColor = new Color(oldColorRaw.Value); + if (newColorRaw.HasValue) + newColor = new Color(newColorRaw.Value); + if (oldPermissionsRaw.HasValue) + oldPermissions = new GuildPermissions(oldPermissionsRaw.Value); + if (newPermissionsRaw.HasValue) + newPermissions = new GuildPermissions(newPermissionsRaw.Value); + + var oldProps = new RoleInfo(oldColor, oldMentionable, oldHoist, oldName, oldPermissions); + var newProps = new RoleInfo(newColor, newMentionable, newHoist, newName, newPermissions); + + return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps); + } + + public ulong RoleId { get; } + public RoleInfo Before { get; } + public RoleInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs new file mode 100644 index 000000000..c94f18271 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs @@ -0,0 +1,23 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class UnbanAuditLogData : IAuditLogData + { + private UnbanAuditLogData(IUser user) + { + Target = user; + } + + internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); + return new UnbanAuditLogData(RestUser.Create(discord, userInfo)); + } + + public IUser Target { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs new file mode 100644 index 000000000..1ae45fb8c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -0,0 +1,44 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookCreateAuditLogData : IAuditLogData + { + private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId) + { + Webhook = webhook; + Name = name; + Type = type; + ChannelId = channelId; + } + + internal static WebhookCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + + var channelId = channelIdModel.NewValue.ToObject(); + var type = typeModel.NewValue.ToObject(); + var name = nameModel.NewValue.ToObject(); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + + return new WebhookCreateAuditLogData(webhook, type, name, channelId); + } + + //Corresponds to the *current* data + public IWebhook Webhook { get; } + + //Corresponds to the *audit log* data + public WebhookType Type { get; } + public string Name { get; } + public ulong ChannelId { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs new file mode 100644 index 000000000..4133d5dff --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookDeleteAuditLogData : IAuditLogData + { + private WebhookDeleteAuditLogData(ulong id, ulong channel, WebhookType type, string name, string avatar) + { + WebhookId = id; + ChannelId = channel; + Name = name; + Type = type; + Avatar = avatar; + } + + internal static WebhookDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var channelId = channelIdModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(); + var name = nameModel.OldValue.ToObject(); + var avatarHash = avatarHashModel?.OldValue?.ToObject(); + + return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); + } + + public ulong WebhookId { get; } + public ulong ChannelId { get; } + public WebhookType Type { get; } + public string Name { get; } + public string Avatar { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs new file mode 100644 index 000000000..26975cc7c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookInfo.cs @@ -0,0 +1,16 @@ +namespace Discord.Rest +{ + public struct WebhookInfo + { + internal WebhookInfo(string name, ulong? channelId, string avatar) + { + Name = name; + ChannelId = channelId; + Avatar = avatar; + } + + public string Name { get; } + public ulong? ChannelId { get; } + public string Avatar { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs new file mode 100644 index 000000000..54da42a8b --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class WebhookUpdateAuditLogData : IAuditLogData + { + private WebhookUpdateAuditLogData(IWebhook webhook, WebhookInfo before, WebhookInfo after) + { + Webhook = webhook; + Before = before; + After = after; + } + + internal static WebhookUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) + { + var changes = entry.Changes; + + var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); + var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); + var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + var oldName = nameModel?.OldValue?.ToObject(); + var oldChannelId = channelIdModel?.OldValue?.ToObject(); + var oldAvatar = avatarHashModel?.OldValue?.ToObject(); + var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); + + var newName = nameModel?.NewValue?.ToObject(); + var newChannelId = channelIdModel?.NewValue?.ToObject(); + var newAvatar = avatarHashModel?.NewValue?.ToObject(); + var after = new WebhookInfo(newName, newChannelId, newAvatar); + + var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); + var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + + return new WebhookUpdateAuditLogData(webhook, before, after); + } + + //Again, the *current* data + public IWebhook Webhook { get; } + + //And the *audit log* data + public WebhookInfo Before { get; } + public WebhookInfo After { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs new file mode 100644 index 000000000..9e30a5014 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -0,0 +1,38 @@ +using System.Linq; + +using Model = Discord.API.AuditLog; +using EntryModel = Discord.API.AuditLogEntry; + +namespace Discord.Rest +{ + public class RestAuditLogEntry : RestEntity, IAuditLogEntry + { + private RestAuditLogEntry(BaseDiscordClient discord, Model fullLog, EntryModel model, IUser user) + : base(discord, model.Id) + { + Action = model.Action; + Data = AuditLogHelper.CreateData(discord, fullLog, model); + User = user; + Reason = model.Reason; + } + + internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) + { + var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); + IUser user = null; + if (userInfo != null) + user = RestUser.Create(discord, userInfo); + + return new RestAuditLogEntry(discord, fullLog, model, user); + } + + /// + public ActionType Action { get; } + /// + public IAuditLogData Data { get; } + /// + public IUser User { get; } + /// + public string Reason { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index e3fd1a8c3..6ccb02f93 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -268,6 +268,35 @@ namespace Discord.Rest return model.Pruned; } + // Audit logs + public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client, + ulong? from, int? limit, RequestOptions options) + { + return new PagedAsyncEnumerable( + DiscordConfig.MaxAuditLogEntriesPerBatch, + async (info, ct) => + { + var args = new GetAuditLogsParams + { + Limit = info.PageSize + }; + if (info.Position != null) + args.BeforeEntryId = info.Position.Value; + var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); + return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) + return false; + info.Position = lastPage.Min(x => x.Id); + return true; + }, + start: from, + count: limit + ); + } + //Webhooks public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index a7123930a..8d1937953 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -268,6 +268,10 @@ namespace Discord.Rest public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + //Webhooks public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); @@ -429,6 +433,14 @@ namespace Discord.Rest } Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } + async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options); async Task> IGuild.GetWebhooksAsync(RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 3dbd1160c..e332fcd9a 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -438,6 +438,10 @@ namespace Discord.WebSocket _downloaderPromise.TrySetResultAsync(true); } + //Audit logs + public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); + //Webhooks public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); @@ -703,6 +707,14 @@ namespace Discord.WebSocket Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); + async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); + else + return ImmutableArray.Create(); + } + async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options); async Task> IGuild.GetWebhooksAsync(RequestOptions options) From 32fc2df21b1840fd913ccce80c0686e8e853b43d Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sat, 12 May 2018 20:47:44 -0400 Subject: [PATCH 024/106] Remove unused field in EmbedFieldBuilder. (#1018) --- src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 62834ebf3..04f4f6884 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -231,7 +231,6 @@ namespace Discord { private string _name; private string _value; - private EmbedField _field; public const int MaxFieldNameLength = 256; public const int MaxFieldValueLength = 1024; From e764dafe083bf5db62091ac1f6b3d438eee38882 Mon Sep 17 00:00:00 2001 From: Quahu Date: Sun, 13 May 2018 15:34:40 +0200 Subject: [PATCH 025/106] Add ViewChannel to Voice channel permissions (#1059) Previously, Voice channels did not have ViewChannel in their "all" permissions --- src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index ef10ee106..038b18e2e 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets a ChannelPermissions that grants all permissions for text channels. public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for voice channels. - public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); + public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001); /// Gets a ChannelPermissions that grants all permissions for category channels. public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for direct message channels. From 79811d0e618c011e6cca542811031d824650dae5 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Thu, 24 May 2018 17:35:38 -0500 Subject: [PATCH 026/106] Paginate reactions - solved #1007 (#1022) * Cleaned up and refactored slightly * Resolves #971 * Adds support for default avatars and resolves #971 * Amendment * Final amendment * Paginating reactions * Amendments based on feedback * Further amendment based on review * Final(?) amendment * Removes default limit and after user id * Removes fromUserId; cleans up model creation; replaces function with individual parameters --- src/Discord.Net.Core/DiscordConfig.cs | 3 +- .../Entities/Messages/IUserMessage.cs | 4 +- .../API/Rest/GetReactionUsersParams.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 +- .../Entities/Messages/MessageHelper.cs | 37 ++++++++++++++++--- .../Entities/Messages/RestUserMessage.cs | 4 +- .../Entities/Messages/SocketUserMessage.cs | 4 +- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 1db3271e5..5cdf6a77e 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace Discord { @@ -20,6 +20,7 @@ namespace Discord public const int MaxMessagesPerBatch = 100; public const int MaxUsersPerBatch = 1000; public const int MaxGuildsPerBatch = 100; + public const int MaxUserReactionsPerBatch = 100; public const int MaxAuditLogEntriesPerBatch = 100; /// Gets or sets how a request should act in the case of an error, by default. diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 52df187f8..36ee725ff 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -23,7 +23,7 @@ namespace Discord /// Removes all reactions from this message. Task RemoveAllReactionsAsync(RequestOptions options = null); /// Gets all users that reacted to a message with a given emote - Task> GetReactionUsersAsync(IEmote emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null); + IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); /// Transforms this message's text into a human readable form by resolving its tags. string Resolve( diff --git a/src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs b/src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs index d70da5632..a0967bb75 100644 --- a/src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetReactionUsersParams.cs @@ -1,4 +1,4 @@ -namespace Discord.API.Rest +namespace Discord.API.Rest { internal class GetReactionUsersParams { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 8b641fb6a..85e04f962 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -618,11 +618,11 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); - Preconditions.AtMost(args.Limit, DiscordConfig.MaxUsersPerBatch, nameof(args.Limit)); + Preconditions.AtMost(args.Limit, DiscordConfig.MaxUserReactionsPerBatch, nameof(args.Limit)); Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(int.MaxValue); + int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch); ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); var ids = new BucketIds(channelId: channelId); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 8ae41cc37..f7ce3ded0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -46,13 +46,38 @@ namespace Discord.Rest await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options); } - public static async Task> GetReactionUsersAsync(IMessage msg, IEmote emote, - Action func, BaseDiscordClient client, RequestOptions options) + public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote, + int? limit, BaseDiscordClient client, RequestOptions options) { - var args = new GetReactionUsersParams(); - func(args); - string emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name); - return (await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false)).Select(u => RestUser.Create(client, u)).ToImmutableArray(); + Preconditions.NotNull(emote, nameof(emote)); + var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name); + + return new PagedAsyncEnumerable( + DiscordConfig.MaxUserReactionsPerBatch, + async (info, ct) => + { + var args = new GetReactionUsersParams + { + Limit = info.PageSize + }; + + if (info.Position != null) + args.AfterUserId = info.Position.Value; + + var models = await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false); + return models.Select(x => RestUser.Create(client, x)).ToImmutableArray(); + }, + nextPage: (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxUsersPerBatch) + return false; + + info.Position = lastPage.Max(x => x.Id); + return true; + }, + count: limit + ); + } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 0d1f3be2b..7354cc4af 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -136,8 +136,8 @@ namespace Discord.Rest => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - public Task> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create(); }, Discord, options); + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); public Task PinAsync(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 5489ad2bb..f8c15a986 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -130,8 +130,8 @@ namespace Discord.WebSocket => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - public Task> GetReactionUsersAsync(IEmote emote, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emote, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create(); }, Discord, options); + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); public Task PinAsync(RequestOptions options = null) => MessageHelper.PinAsync(this, Discord, options); From 0ba8b063ad98144232606f422574f9741fd95b7a Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Thu, 24 May 2018 19:23:44 -0400 Subject: [PATCH 027/106] Makes text parameter of sending messages optional (#1042) commit 114e5b431b26669bcdaac9f84792a216ad67186f Author: HelpfulStranger999 Date: Sat Apr 28 19:08:35 2018 -0500 Fixes lack of default value for tts commit 1fd8c70c5346ff0c4fdb0093c0fc7fb4b3c8db2c Author: HelpfulStranger999 Date: Sat Apr 28 15:21:11 2018 -0500 Makes text parameter of sending messages optional --- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 2 +- src/Discord.Net.Core/Extensions/UserExtensions.cs | 2 +- .../Entities/Channels/IRestMessageChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 2 +- .../Entities/Channels/RpcVirtualMessageChannel.cs | 6 +++--- .../Entities/Channels/ISocketMessageChannel.cs | 2 +- .../Entities/Channels/SocketDMChannel.cs | 2 +- .../Entities/Channels/SocketGroupChannel.cs | 2 +- .../Entities/Channels/SocketTextChannel.cs | 2 +- src/Discord.Net.Webhook/DiscordWebhookClient.cs | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 5a6e5df59..099b9da43 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -8,7 +8,7 @@ namespace Discord public interface IMessageChannel : IChannel { /// Sends a message to this message channel. - Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); + Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index d3e968e39..3296d00fd 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -9,7 +9,7 @@ namespace Discord /// Sends a message to the user via DM. /// public static async Task SendMessageAsync(this IUser user, - string text, + string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 8ab6e9819..b6f891f40 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -7,7 +7,7 @@ namespace Discord.Rest public interface IRestMessageChannel : IMessageChannel { /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 08acdf32b..f65ce79de 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -63,7 +63,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index a1868573e..af9d80d84 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -76,7 +76,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 600b197d6..8a33ebd05 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -58,7 +58,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 3b6a68193..14cbc33f1 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -33,13 +33,13 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM - public Task SendFileAsync(string filePath, string text, bool isTTS, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); #endif - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task TriggerTypingAsync(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 026bd8378..971dbe8f1 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); #if FILESYSTEM /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 451240e66..764431ae1 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -67,7 +67,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index d46c5fc59..1d68109ee 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -95,7 +95,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index ec7615b55..f776c9956 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -75,7 +75,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 59cc8f3e7..2dea1c1d1 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -63,7 +63,7 @@ namespace Discord.Webhook => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); /// Sends a message using to the channel for this webhook. Returns the ID of the created message. - public Task SendMessageAsync(string text, bool isTTS = false, IEnumerable embeds = null, + public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, options); From bf5275e071b78a25fd4501a930e69c5dc8cd3b19 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 24 May 2018 19:36:00 -0400 Subject: [PATCH 028/106] Add ability to specify parameters on channel creation (#1020) commit 07bca5b31a3580d55278878eabb56a82973f8c8f Author: Joe4evr Date: Fri Apr 6 09:44:50 2018 +0200 Add ability to specify parameters on channel creation --- .../Entities/Guilds/IGuild.cs | 4 ++-- .../API/Rest/CreateGuildChannelParams.cs | 13 +++++++++- .../Entities/Guilds/GuildHelper.cs | 24 +++++++++++++++---- .../Entities/Guilds/RestGuild.cs | 16 ++++++------- .../Entities/Guilds/SocketGuild.cs | 16 ++++++------- 5 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 19d2ee81c..f6e13e0e1 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -109,9 +109,9 @@ namespace Discord Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Creates a new text channel. - Task CreateTextChannelAsync(string name, RequestOptions options = null); + Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null); /// Creates a new voice channel. - Task CreateVoiceChannelAsync(string name, RequestOptions options = null); + Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null); /// Creates a new channel category. Task CreateCategoryAsync(string name, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs index bae677148..05cdf4b8a 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,9 +10,20 @@ namespace Discord.API.Rest public string Name { get; } [JsonProperty("type")] public ChannelType Type { get; } + [JsonProperty("parent_id")] + public Optional CategoryId { get; set; } + //Text channels + [JsonProperty("topic")] + public Optional Topic { get; set; } + [JsonProperty("nsfw")] + public Optional IsNsfw { get; set; } + + //Voice channels [JsonProperty("bitrate")] public Optional Bitrate { get; set; } + [JsonProperty("user_limit")] + public Optional UserLimit { get; set; } public CreateGuildChannelParams(string name, ChannelType type) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 6ccb02f93..c76b41a91 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -145,20 +145,36 @@ namespace Discord.Rest return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); } public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, - string name, RequestOptions options) + string name, RequestOptions options, Action func = null) { if (name == null) throw new ArgumentNullException(nameof(name)); - var args = new CreateGuildChannelParams(name, ChannelType.Text); + var props = new TextChannelProperties(); + func?.Invoke(props); + + var args = new CreateGuildChannelParams(name, ChannelType.Text) + { + CategoryId = props.CategoryId, + Topic = props.Topic, + IsNsfw = props.IsNsfw + }; var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); } public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, - string name, RequestOptions options) + string name, RequestOptions options, Action func = null) { if (name == null) throw new ArgumentNullException(nameof(name)); - var args = new CreateGuildChannelParams(name, ChannelType.Voice); + var props = new VoiceChannelProperties(); + func?.Invoke(props); + + var args = new CreateGuildChannelParams(name, ChannelType.Voice) + { + CategoryId = props.CategoryId, + Bitrate = props.Bitrate, + UserLimit = props.UserLimit + }; var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 8d1937953..91b548be4 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -222,10 +222,10 @@ namespace Discord.Rest } return null; } - public Task CreateTextChannelAsync(string name, RequestOptions options = null) - => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); - public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) - => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); + public Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null) + => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); + public Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -383,10 +383,10 @@ namespace Discord.Rest else return null; } - async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) - => await CreateTextChannelAsync(name, options).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) - => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); + async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options, Action func) + => await CreateTextChannelAsync(name, options, func).ConfigureAwait(false); + async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options, Action func) + => await CreateVoiceChannelAsync(name, options, func).ConfigureAwait(false); async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e332fcd9a..a6ef1925b 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -315,10 +315,10 @@ namespace Discord.WebSocket => GetChannel(id) as SocketTextChannel; public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; - public Task CreateTextChannelAsync(string name, RequestOptions options = null) - => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); - public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) - => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options); + public Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null) + => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); + public Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null) + => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -678,10 +678,10 @@ namespace Discord.WebSocket => Task.FromResult(EmbedChannel); Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(SystemChannel); - async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options) - => await CreateTextChannelAsync(name, options).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options) - => await CreateVoiceChannelAsync(name, options).ConfigureAwait(false); + async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options, Action func) + => await CreateTextChannelAsync(name, options, func).ConfigureAwait(false); + async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options, Action func) + => await CreateVoiceChannelAsync(name, options, func).ConfigureAwait(false); async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); From 5023357a60bd5ae454b35426ddd279fb3284b7e8 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Thu, 24 May 2018 19:36:33 -0400 Subject: [PATCH 029/106] codefix #1020: RequestOptions should always be sorted last --- src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 12 ++++++------ .../Entities/Guilds/SocketGuild.cs | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index f6e13e0e1..4c1c274d0 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -109,9 +109,9 @@ namespace Discord Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Creates a new text channel. - Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null); + Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null); /// Creates a new voice channel. - Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null); + Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null); /// Creates a new channel category. Task CreateCategoryAsync(string name, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 91b548be4..357e22c85 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -222,9 +222,9 @@ namespace Discord.Rest } return null; } - public Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null) + public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); - public Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null) + public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -383,10 +383,10 @@ namespace Discord.Rest else return null; } - async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options, Action func) - => await CreateTextChannelAsync(name, options, func).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options, Action func) - => await CreateVoiceChannelAsync(name, options, func).ConfigureAwait(false); + async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) + => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) + => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index a6ef1925b..14263f0af 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -315,9 +315,9 @@ namespace Discord.WebSocket => GetChannel(id) as SocketTextChannel; public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; - public Task CreateTextChannelAsync(string name, RequestOptions options = null, Action func = null) + public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); - public Task CreateVoiceChannelAsync(string name, RequestOptions options = null, Action func = null) + public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -678,10 +678,10 @@ namespace Discord.WebSocket => Task.FromResult(EmbedChannel); Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) => Task.FromResult(SystemChannel); - async Task IGuild.CreateTextChannelAsync(string name, RequestOptions options, Action func) - => await CreateTextChannelAsync(name, options, func).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, RequestOptions options, Action func) - => await CreateVoiceChannelAsync(name, options, func).ConfigureAwait(false); + async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) + => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) + => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); From c275e575289073fe42ead725de97b97301c7dc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?o=20Acid=20Chicken=20=28=E7=A1=AB=E7=A1=AB=E2=96=92=7EE?= =?UTF-8?q?=E2=96=92=E2=96=92=E2=96=92=7EO=29?= Date: Thu, 24 May 2018 19:52:25 -0400 Subject: [PATCH 030/106] Add support casting System.Drawing.Color to Discord.Color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit fa3303426766a59e7aa4d67e8b50826cfe7204ec Author: Acid Chicken (硫酸鶏) Date: Sun Apr 29 13:50:56 2018 +0900 Use built-in symbol refs: https://github.com/RogueException/Discord.Net/pull/1043#issuecomment-385223999 commit 27ea82668587960925b73f97c924c9d39ee71f7b Author: Acid Chicken (硫酸鶏) Date: Sun Apr 29 11:29:31 2018 +0900 Add support casting System.Drawing.Color to Discord.Color commit 1ab9de24978ff24a018767f80dc95ba19b616988 Merge: f5bb99c7 a4d1e2bc Author: Acid Chicken (硫酸鶏) Date: Sun Apr 29 10:16:46 2018 +0900 Merge remote-tracking branch 'upstream/dev' into dev commit f5bb99c77d0fecec21ad769778d17144c047d8b1 Merge: 3be8e40d b8b59d97 Author: Acid Chicken (硫酸鶏) Date: Tue Apr 10 11:07:36 2018 +0900 Merge remote-tracking branch 'upstream/dev' into dev commit 3be8e40d3814fe8455af475b8641ee07a8f368d6 Merge: c692306f 9d77a3cd Author: Acid Chicken (硫酸鶏) Date: Sun Jan 7 15:05:31 2018 +0900 Merge remote-tracking branch 'upstream/dev' into dev commit c692306fcc1e86ab92dd10683d3719f16c02a249 Author: Acid Chicken (硫酸鶏) Date: Sat Nov 11 10:49:00 2017 +0900 Add target of the internal fields commit 2d08f9a655b4949c1177f778d0f499047484a537 Author: Acid Chicken (硫酸鶏) Date: Sat Nov 11 10:17:42 2017 +0900 Add some more extension commit 4f19b835ffe8c64a93a9b4659e60b03ac797760f Author: Acid Chicken (硫酸鶏) Date: Sat Nov 11 01:19:11 2017 +0900 Add naming rules commit af756cd9feb630baadbf6025cbb079cd9e1f45cb Author: Acid Chicken (硫酸鶏) Date: Sat Nov 11 00:35:30 2017 +0900 Add basic .NET style rules commit 503ece558b4f07bd8008157d3aeb6a4e7100d349 Author: Acid Chicken (硫酸鶏) Date: Fri Nov 10 22:36:52 2017 +0900 Add EditorConfig --- src/Discord.Net.Core/Discord.Net.Core.csproj | 4 ++-- src/Discord.Net.Core/Entities/Roles/Color.cs | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 7565fa178..321803114 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -4,8 +4,8 @@ Discord.Net.Core Discord The core components for the Discord.Net library. - net45;netstandard1.1;netstandard1.3 - netstandard1.1;netstandard1.3 + net45;netstandard1.1;netstandard1.3;netstandard2.0 + netstandard1.1;netstandard1.3;netstandard2.0 diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 89e76df6d..0bb04d339 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -1,5 +1,8 @@ using System; using System.Diagnostics; +#if NETSTANDARD2_0 || NET45 +using StandardColor = System.Drawing.Color; +#endif namespace Discord { @@ -96,7 +99,14 @@ namespace Discord ((uint)(g * 255.0f) << 8) | (uint)(b * 255.0f); } - + +#if NETSTANDARD2_0 || NET45 + public static implicit operator StandardColor(Color color) => + StandardColor.FromArgb((int)color.RawValue); + public static explicit operator Color(StandardColor color) => + new Color((uint)color.ToArgb() << 8 >> 8); +#endif + public override string ToString() => $"#{Convert.ToString(RawValue, 16)}"; private string DebuggerDisplay => From b52af7ae7caaddd79101aeb9f5af2ce482890bc2 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 25 May 2018 01:59:32 +0200 Subject: [PATCH 031/106] Add a dedicated TimeSpan reader so it doesn't suck (#1005) * Add a dedicated TimeSpan reader so it doesn't suck * Pass input as lower case --- src/Discord.Net.Commands/CommandService.cs | 4 +++ src/Discord.Net.Commands/PrimitiveParsers.cs | 4 +-- .../Readers/TimeSpanTypeReader.cs | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index c996f7334..b20c231c3 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -65,6 +65,10 @@ namespace Discord.Commands _defaultTypeReaders[typeof(Nullable<>).MakeGenericType(type)] = NullableTypeReader.Create(type, _defaultTypeReaders[type]); } + var tsreader = new TimeSpanTypeReader(); + _defaultTypeReaders[typeof(TimeSpan)] = tsreader; + _defaultTypeReaders[typeof(TimeSpan?)] = NullableTypeReader.Create(typeof(TimeSpan), tsreader); + _defaultTypeReaders[typeof(string)] = new PrimitiveTypeReader((string x, out string y) => { y = x; return true; }, 0); diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index 6a54ba402..bf0622c28 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -29,7 +29,7 @@ namespace Discord.Commands parserBuilder[typeof(decimal)] = (TryParseDelegate)decimal.TryParse; parserBuilder[typeof(DateTime)] = (TryParseDelegate)DateTime.TryParse; parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate)DateTimeOffset.TryParse; - parserBuilder[typeof(TimeSpan)] = (TryParseDelegate)TimeSpan.TryParse; + //parserBuilder[typeof(TimeSpan)] = (TryParseDelegate)TimeSpan.TryParse; parserBuilder[typeof(char)] = (TryParseDelegate)char.TryParse; return parserBuilder.ToImmutable(); } diff --git a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs new file mode 100644 index 000000000..31ab9d821 --- /dev/null +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + internal class TimeSpanTypeReader : TypeReader + { + private static readonly string[] _formats = new[] + { + "%d'd'%h'h'%m'm'%s's'", //4d3h2m1s + "%d'd'%h'h'%m'm'", //4d3h2m + "%d'd'%h'h'%s's'", //4d3h 1s + "%d'd'%h'h'", //4d3h + "%d'd'%m'm'%s's'", //4d 2m1s + "%d'd'%m'm'", //4d 2m + "%d'd'%s's'", //4d 1s + "%d'd'", //4d + "%h'h'%m'm'%s's'", // 3h2m1s + "%h'h'%m'm'", // 3h2m + "%h'h'%s's'", // 3h 1s + "%h'h'", // 3h + "%m'm'%s's'", // 2m1s + "%m'm'", // 2m + "%s's'", // 1s + }; + + public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + { + return (TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan)) + ? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) + : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); + } + } +} From cee71ef35a450802ef35090d9eaf167ca39306e8 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 24 May 2018 17:07:37 -0700 Subject: [PATCH 032/106] Add support for parsing multiple types of quotation marks in commands, Fix #942 (#943) * Add ability to support different types of quotation marks * Added normal quotation mark to list of aliases, removed single quote mark * clean up leftover changes from testing * change quotation mark parsing to use a map of matching pairs * remove commented out code * Fix conventions of the command parser utility functions * change storage type of alias dictionary to be IReadOnlyDictionary * revert type of CommandServiceConfig QuotationMarkAliasMap to Dictionary * minor formatting changes to CommandParser * remove unnecessary whitespace * Move aliases outside of CommandInfo class * copy IReadOnlyDictionary to ImmutableDictionary * minor syntax changes in CommandServiceConfig * add newline before namespace for consistency * newline formatting tweak * simplification of GetMatch method for CommandParser * add more quote unicode punctuation pairs * add check for null value when building ImmutableDictionary * Move default alias map into a separate source file * Ensure that the collection passed into command service is not null --- src/Discord.Net.Commands/CommandParser.cs | 34 +++++-- src/Discord.Net.Commands/CommandService.cs | 5 +- .../CommandServiceConfig.cs | 5 + src/Discord.Net.Commands/Info/CommandInfo.cs | 5 +- .../Utilities/QuotationAliasUtils.cs | 95 +++++++++++++++++++ 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 64daf841e..9ce4e1469 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Text; using System.Threading.Tasks; @@ -13,8 +14,7 @@ namespace Discord.Commands Parameter, QuotedParameter } - - public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) + public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary aliasMap) { ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); @@ -24,7 +24,27 @@ namespace Discord.Commands var argList = ImmutableArray.CreateBuilder(); var paramList = ImmutableArray.CreateBuilder(); bool isEscaping = false; - char c; + char c, matchQuote = '\0'; + + // local helper functions + bool IsOpenQuote(IReadOnlyDictionary dict, char ch) + { + // return if the key is contained in the dictionary if it is populated + if (dict.Count != 0) + return dict.ContainsKey(ch); + // or otherwise if it is the default double quote + return c == '\"'; + } + + char GetMatch(IReadOnlyDictionary dict, char ch) + { + // get the corresponding value for the key, if it exists + // and if the dictionary is populated + if (dict.Count != 0 && dict.TryGetValue(c, out var value)) + return value; + // or get the default pair of the default double quote + return '\"'; + } for (int curPos = startPos; curPos <= endPos; curPos++) { @@ -74,9 +94,11 @@ namespace Discord.Commands argBuilder.Append(c); continue; } - if (c == '\"') + + if (IsOpenQuote(aliasMap, c)) { curPart = ParserPart.QuotedParameter; + matchQuote = GetMatch(aliasMap, c); continue; } curPart = ParserPart.Parameter; @@ -97,7 +119,7 @@ namespace Discord.Commands } else if (curPart == ParserPart.QuotedParameter) { - if (c == '\"') + if (c == matchQuote) { argString = argBuilder.ToString(); //Remove quotes lastArgEndPos = curPos + 1; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b20c231c3..f75653ccf 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -32,6 +32,7 @@ namespace Discord.Commands internal readonly RunMode _defaultRunMode; internal readonly Logger _cmdLogger; internal readonly LogManager _logManager; + internal readonly IReadOnlyDictionary _quotationMarkAliasMap; public IEnumerable Modules => _moduleDefs.Select(x => x); public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); @@ -45,6 +46,7 @@ namespace Discord.Commands _ignoreExtraArgs = config.IgnoreExtraArgs; _separatorChar = config.SeparatorChar; _defaultRunMode = config.DefaultRunMode; + _quotationMarkAliasMap = (config.QuotationMarkAliasMap ?? new Dictionary()).ToImmutableDictionary(); if (_defaultRunMode == RunMode.Default) throw new InvalidOperationException("The default run mode cannot be set to Default."); @@ -337,7 +339,6 @@ namespace Discord.Commands public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; - var searchResult = Search(context, input); if (!searchResult.IsSuccess) return searchResult; diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index f5cd14fef..00091634d 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Discord.Commands { @@ -18,6 +19,10 @@ namespace Discord.Commands /// Determines whether RunMode.Sync commands should push exceptions up to the caller. public bool ThrowOnError { get; set; } = true; + /// Collection of aliases that can wrap strings for command parsing. + /// represents the opening quotation mark and the value is the corresponding closing mark. + public Dictionary QuotationMarkAliasMap { get; set; } = QuotationAliasUtils.GetDefaultAliasMap; + /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 1a9cf69c5..df0bb297b 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -1,4 +1,4 @@ -using Discord.Commands.Builders; +using Discord.Commands.Builders; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -121,7 +121,8 @@ namespace Discord.Commands return ParseResult.FromError(preconditionResult); string input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false); + + return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false); } public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) diff --git a/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs new file mode 100644 index 000000000..15a08b9b3 --- /dev/null +++ b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Globalization; + +namespace Discord.Commands +{ + /// + /// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig + /// + internal static class QuotationAliasUtils + { + /// + /// Generates an IEnumerable of characters representing open-close pairs of + /// quotation punctuation. + /// + internal static Dictionary GetDefaultAliasMap + { + get + { + // Output of a gist provided by https://gist.github.com/ufcpp + // https://gist.github.com/ufcpp/5b2cf9a9bf7d0b8743714a0b88f7edc5 + // This was not used for the implementation because of incompatibility with netstandard1.1 + return new Dictionary { + {'\"', '\"' }, + {'«', '»' }, + {'‘', '’' }, + {'“', '”' }, + {'„', '‟' }, + {'‹', '›' }, + {'‚', '‛' }, + {'《', '》' }, + {'〈', '〉' }, + {'「', '」' }, + {'『', '』' }, + {'〝', '〞' }, + {'﹁', '﹂' }, + {'﹃', '﹄' }, + {'"', '"' }, + {''', ''' }, + {'「', '」' }, + {'(', ')' }, + {'༺', '༻' }, + {'༼', '༽' }, + {'᚛', '᚜' }, + {'⁅', '⁆' }, + {'⌈', '⌉' }, + {'⌊', '⌋' }, + {'❨', '❩' }, + {'❪', '❫' }, + {'❬', '❭' }, + {'❮', '❯' }, + {'❰', '❱' }, + {'❲', '❳' }, + {'❴', '❵' }, + {'⟅', '⟆' }, + {'⟦', '⟧' }, + {'⟨', '⟩' }, + {'⟪', '⟫' }, + {'⟬', '⟭' }, + {'⟮', '⟯' }, + {'⦃', '⦄' }, + {'⦅', '⦆' }, + {'⦇', '⦈' }, + {'⦉', '⦊' }, + {'⦋', '⦌' }, + {'⦍', '⦎' }, + {'⦏', '⦐' }, + {'⦑', '⦒' }, + {'⦓', '⦔' }, + {'⦕', '⦖' }, + {'⦗', '⦘' }, + {'⧘', '⧙' }, + {'⧚', '⧛' }, + {'⧼', '⧽' }, + {'⸂', '⸃' }, + {'⸄', '⸅' }, + {'⸉', '⸊' }, + {'⸌', '⸍' }, + {'⸜', '⸝' }, + {'⸠', '⸡' }, + {'⸢', '⸣' }, + {'⸤', '⸥' }, + {'⸦', '⸧' }, + {'⸨', '⸩' }, + {'【', '】'}, + {'〔', '〕' }, + {'〖', '〗' }, + {'〘', '〙' }, + {'〚', '〛' } + }; + } + } + } +} From bc6009ec7207e16aedecdc658dc59b8ac02c400e Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Fri, 25 May 2018 08:08:51 +0800 Subject: [PATCH 033/106] Implement IMessageChannel#DeleteMessageAsync (#996) * Implement DeleteMessageAsync * Refer to MessageHelper instead of duplicating call * Fix refactor error --- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 5 +++++ src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 4 ++++ src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 5 +++++ src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs | 5 +++++ src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 5 +++++ .../Entities/Channels/RpcVirtualMessageChannel.cs | 5 +++++ src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 6 ++++-- .../Entities/Channels/SocketDMChannel.cs | 5 +++++ .../Entities/Channels/SocketGroupChannel.cs | 5 +++++ .../Entities/Channels/SocketTextChannel.cs | 5 +++++ 10 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 099b9da43..262b865ea 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -30,6 +30,11 @@ namespace Discord /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(RequestOptions options = null); + /// Deletes a message based on the message ID in this channel. + Task DeleteMessageAsync(ulong messageId, RequestOptions options = null); + /// Deletes a message based on the provided message in this channel. + Task DeleteMessageAsync(IMessage message, RequestOptions options = null); + /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. Task TriggerTypingAsync(RequestOptions options = null); /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned object is disposed. diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 6784f7f6a..dbbabbd72 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -185,6 +185,10 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } + public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, + RequestOptions options) + => MessageHelper.DeleteAsync(channel.Id, messageId, client, options); + public static async Task DeleteMessagesAsync(ITextChannel channel, BaseDiscordClient client, IEnumerable messageIds, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index f65ce79de..21cd579e6 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -72,6 +72,11 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index af9d80d84..901016a6b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -76,6 +76,11 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); #if FILESYSTEM diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 8a33ebd05..841aad666 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -67,6 +67,11 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 14cbc33f1..8f69388d4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -42,6 +42,11 @@ namespace Discord.Rest public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index f7ce3ded0..3dc3e74e9 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -25,10 +25,12 @@ namespace Discord.Rest }; return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, + public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) + => DeleteAsync(msg.Channel.Id, msg.Id, client, options); + public static async Task DeleteAsync(ulong channelId, ulong msgId, BaseDiscordClient client, RequestOptions options) { - await client.ApiClient.DeleteMessageAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); + await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); } public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 764431ae1..d95d45890 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -76,6 +76,11 @@ namespace Discord.WebSocket public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 1d68109ee..3d6f60db2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -104,6 +104,11 @@ namespace Discord.WebSocket public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index f776c9956..9373c431a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -89,6 +89,11 @@ namespace Discord.WebSocket public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) From 5f084adf94f3215b6d68c3a8b61cbfad7376f18c Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Thu, 24 May 2018 19:17:19 -0500 Subject: [PATCH 034/106] Deprecates ReadMessages, introduces ViewChannel (#1033) * Deprecates ReadMessages, introduces ViewChannel * Adds period and comma somehow missed --- .../Entities/Permissions/GuildPermission.cs | 6 ++++-- .../Entities/Permissions/GuildPermissions.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 8469fd304..e90b4269e 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -16,7 +16,9 @@ namespace Discord // Text AddReactions = 0x00_00_00_40, ViewAuditLog = 0x00_00_00_80, - ReadMessages = 0x00_00_04_00, + [Obsolete("Use ViewChannel instead.")] + ReadMessages = ViewChannel, + ViewChannel = 0x00_00_04_00, SendMessages = 0x00_00_08_00, SendTTSMessages = 0x00_00_10_00, ManageMessages = 0x00_00_20_00, diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index a880e62ca..dcebeb20d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; namespace Discord @@ -35,7 +36,10 @@ namespace Discord public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); /// If True, a user may join channels. - public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); + [Obsolete("Use ViewChannel instead.")] + public bool ReadMessages => ViewChannel; + /// If True, a user may view channels. + public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); /// If True, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, GuildPermission.SendMessages); /// If True, a user may send text-to-speech messages. From bbbac85c46162d7bb44ecdd68194ab79db25037d Mon Sep 17 00:00:00 2001 From: Hawx Date: Fri, 25 May 2018 20:14:28 +0800 Subject: [PATCH 035/106] Update Dependencies and support NS2.0 builds (#1046) * Update deps, fix test warnings. * Support ns2.0 * Fix typo * Remove ns1.1 support * Net.Http and Net.Websockets.Client are not needed in ns2.0 * Move to net46 per volt * Remove ns1.3 constants --- Discord.Net.targets | 8 +-- .../02_commands_framework.csproj | 2 +- .../Discord.Net.Analyzers.csproj | 2 +- .../Discord.Net.Commands.csproj | 10 ++-- src/Discord.Net.Core/Discord.Net.Core.csproj | 10 ++-- .../Entities/Channels/IMessageChannel.cs | 3 +- src/Discord.Net.Core/Entities/Image.cs | 6 +-- .../Extensions/UserExtensions.cs | 2 - src/Discord.Net.Core/Logging/LogManager.cs | 23 ++++---- src/Discord.Net.Core/Logging/Logger.cs | 25 +++++---- src/Discord.Net.Core/Utils/DateTimeUtils.cs | 47 +---------------- src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 4 +- src/Discord.Net.Rest/Discord.Net.Rest.csproj | 13 +++-- .../Entities/Channels/ChannelHelper.cs | 3 +- .../Entities/Channels/IRestMessageChannel.cs | 3 +- .../Entities/Channels/RestDMChannel.cs | 7 ++- .../Entities/Channels/RestGroupChannel.cs | 7 ++- .../Entities/Channels/RestTextChannel.cs | 7 ++- .../Channels/RpcVirtualMessageChannel.cs | 6 +-- .../Net/Converters/DiscordContractResolver.cs | 3 +- .../Net/Queue/RequestQueueBucket.cs | 2 +- src/Discord.Net.Rest/Net/RateLimitInfo.cs | 4 +- .../Discord.Net.WebSocket.csproj | 8 +-- .../DiscordSocketClient.cs | 2 +- .../DiscordVoiceApiClient.cs | 4 +- .../Channels/ISocketMessageChannel.cs | 3 +- .../Entities/Channels/SocketDMChannel.cs | 6 +-- .../Entities/Channels/SocketGroupChannel.cs | 8 +-- .../Entities/Channels/SocketTextChannel.cs | 8 +-- .../Net/DefaultUdpSocket.cs | 6 --- .../Net/DefaultUdpSocketProvider.cs | 10 +--- .../Net/DefaultWebSocketClient.cs | 13 ++--- .../Net/DefaultWebSocketClientProvider.cs | 10 +--- .../Discord.Net.Webhook.csproj | 4 +- .../DiscordWebhookClient.cs | 3 +- .../WebhookClientHelper.cs | 4 +- .../Discord.Net.Tests.csproj | 10 ++-- .../Tests.ChannelPermissions.cs | 2 +- test/Discord.Net.Tests/Tests.Channels.cs | 52 +++++++++---------- test/Discord.Net.Tests/Tests.Colors.cs | 3 +- test/Discord.Net.Tests/Tests.Emotes.cs | 3 +- test/Discord.Net.Tests/Tests.Permissions.cs | 8 +-- 42 files changed, 135 insertions(+), 229 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 958b2053f..079ec7749 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -16,13 +16,7 @@ $(VersionSuffix)-$(BuildNumber) build-$(BuildNumber) - - - $(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET - - - $(DefineConstants);FORMATSTR;UNIXTIME;MSTRYBUFFER;UDPDISPOSE - + $(NoWarn);CS1573;CS1591 true diff --git a/samples/02_commands_framework/02_commands_framework.csproj b/samples/02_commands_framework/02_commands_framework.csproj index 77fdc65e1..f479ee0b0 100644 --- a/samples/02_commands_framework/02_commands_framework.csproj +++ b/samples/02_commands_framework/02_commands_framework.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj index 8ab398ff5..5da3d506d 100644 --- a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj +++ b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj @@ -7,7 +7,7 @@ netstandard1.3 - + diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj index eaac79a55..a754486dd 100644 --- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -1,15 +1,19 @@ - + Discord.Net.Commands Discord.Commands A Discord.Net extension adding support for bot commands. - netstandard1.1 + net46;netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0 - + + + + \ No newline at end of file diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 321803114..6a58367e6 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,15 +1,15 @@ - + Discord.Net.Core Discord The core components for the Discord.Net library. - net45;netstandard1.1;netstandard1.3;netstandard2.0 - netstandard1.1;netstandard1.3;netstandard2.0 + net46;netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0 - - + + diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 262b865ea..ef5a6fa7a 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -9,10 +9,9 @@ namespace Discord { /// Sends a message to this message channel. Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#if FILESYSTEM /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#endif + /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Image.cs b/src/Discord.Net.Core/Entities/Image.cs index c2c997365..3b946ce80 100644 --- a/src/Discord.Net.Core/Entities/Image.cs +++ b/src/Discord.Net.Core/Entities/Image.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace Discord { /// @@ -15,7 +15,7 @@ namespace Discord { Stream = stream; } -#if FILESYSTEM + /// /// Create the image from a file path. /// @@ -27,6 +27,6 @@ namespace Discord { Stream = File.OpenRead(path); } -#endif + } } diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 3296d00fd..951e8ca4b 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -32,7 +32,6 @@ namespace Discord return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); } -#if FILESYSTEM /// /// Sends a file to the user via DM. /// @@ -45,7 +44,6 @@ namespace Discord { return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); } -#endif public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) => user.Guild.AddBanAsync(user, pruneDays, reason, options); diff --git a/src/Discord.Net.Core/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs index 995a5d96a..a69519fa2 100644 --- a/src/Discord.Net.Core/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Logging @@ -35,7 +35,7 @@ namespace Discord.Logging } catch { } } -#if FORMATSTR + public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) { try @@ -45,52 +45,49 @@ namespace Discord.Logging } catch { } } -#endif + public Task ErrorAsync(string source, Exception ex) => LogAsync(LogSeverity.Error, source, ex); public Task ErrorAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Error, source, message, ex); -#if FORMATSTR + public Task ErrorAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Error, source, message, ex); -#endif + public Task WarningAsync(string source, Exception ex) => LogAsync(LogSeverity.Warning, source, ex); public Task WarningAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Warning, source, message, ex); -#if FORMATSTR + public Task WarningAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Warning, source, message, ex); -#endif + public Task InfoAsync(string source, Exception ex) => LogAsync(LogSeverity.Info, source, ex); public Task InfoAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Info, source, message, ex); -#if FORMATSTR public Task InfoAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Info, source, message, ex); -#endif + public Task VerboseAsync(string source, Exception ex) => LogAsync(LogSeverity.Verbose, source, ex); public Task VerboseAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Verbose, source, message, ex); -#if FORMATSTR public Task VerboseAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Verbose, source, message, ex); -#endif + public Task DebugAsync(string source, Exception ex) => LogAsync(LogSeverity.Debug, source, ex); public Task DebugAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Debug, source, message, ex); -#if FORMATSTR public Task DebugAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Debug, source, message, ex); -#endif + public Logger CreateLogger(string name) => new Logger(this, name); diff --git a/src/Discord.Net.Core/Logging/Logger.cs b/src/Discord.Net.Core/Logging/Logger.cs index a8d88b2b4..e71c56992 100644 --- a/src/Discord.Net.Core/Logging/Logger.cs +++ b/src/Discord.Net.Core/Logging/Logger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Logging @@ -20,54 +20,53 @@ namespace Discord.Logging => _manager.LogAsync(severity, Name, exception); public Task LogAsync(LogSeverity severity, string message, Exception exception = null) => _manager.LogAsync(severity, Name, message, exception); -#if FORMATSTR public Task LogAsync(LogSeverity severity, FormattableString message, Exception exception = null) => _manager.LogAsync(severity, Name, message, exception); -#endif + public Task ErrorAsync(Exception exception) => _manager.ErrorAsync(Name, exception); public Task ErrorAsync(string message, Exception exception = null) => _manager.ErrorAsync(Name, message, exception); -#if FORMATSTR + public Task ErrorAsync(FormattableString message, Exception exception = null) => _manager.ErrorAsync(Name, message, exception); -#endif + public Task WarningAsync(Exception exception) => _manager.WarningAsync(Name, exception); public Task WarningAsync(string message, Exception exception = null) => _manager.WarningAsync(Name, message, exception); -#if FORMATSTR + public Task WarningAsync(FormattableString message, Exception exception = null) => _manager.WarningAsync(Name, message, exception); -#endif + public Task InfoAsync(Exception exception) => _manager.InfoAsync(Name, exception); public Task InfoAsync(string message, Exception exception = null) => _manager.InfoAsync(Name, message, exception); -#if FORMATSTR + public Task InfoAsync(FormattableString message, Exception exception = null) => _manager.InfoAsync(Name, message, exception); -#endif + public Task VerboseAsync(Exception exception) => _manager.VerboseAsync(Name, exception); public Task VerboseAsync(string message, Exception exception = null) => _manager.VerboseAsync(Name, message, exception); -#if FORMATSTR + public Task VerboseAsync(FormattableString message, Exception exception = null) => _manager.VerboseAsync(Name, message, exception); -#endif + public Task DebugAsync(Exception exception) => _manager.DebugAsync(Name, exception); public Task DebugAsync(string message, Exception exception = null) => _manager.DebugAsync(Name, message, exception); -#if FORMATSTR + public Task DebugAsync(FormattableString message, Exception exception = null) => _manager.DebugAsync(Name, message, exception); -#endif + } } diff --git a/src/Discord.Net.Core/Utils/DateTimeUtils.cs b/src/Discord.Net.Core/Utils/DateTimeUtils.cs index af2126853..e2a8faa75 100644 --- a/src/Discord.Net.Core/Utils/DateTimeUtils.cs +++ b/src/Discord.Net.Core/Utils/DateTimeUtils.cs @@ -1,57 +1,14 @@ -using System; +using System; namespace Discord { //Source: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/DateTimeOffset.cs internal static class DateTimeUtils { -#if !UNIXTIME - private const long UnixEpochTicks = 621_355_968_000_000_000; - private const long UnixEpochSeconds = 62_135_596_800; - private const long UnixEpochMilliseconds = 62_135_596_800_000; -#endif - public static DateTimeOffset FromTicks(long ticks) => new DateTimeOffset(ticks, TimeSpan.Zero); public static DateTimeOffset? FromTicks(long? ticks) => ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null; - - public static DateTimeOffset FromUnixSeconds(long seconds) - { -#if UNIXTIME - return DateTimeOffset.FromUnixTimeSeconds(seconds); -#else - long ticks = seconds * TimeSpan.TicksPerSecond + UnixEpochTicks; - return new DateTimeOffset(ticks, TimeSpan.Zero); -#endif - } - public static DateTimeOffset FromUnixMilliseconds(long milliseconds) - { -#if UNIXTIME - return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds); -#else - long ticks = milliseconds * TimeSpan.TicksPerMillisecond + UnixEpochTicks; - return new DateTimeOffset(ticks, TimeSpan.Zero); -#endif - } - - public static long ToUnixSeconds(DateTimeOffset dto) - { -#if UNIXTIME - return dto.ToUnixTimeSeconds(); -#else - long seconds = dto.UtcDateTime.Ticks / TimeSpan.TicksPerSecond; - return seconds - UnixEpochSeconds; -#endif - } - public static long ToUnixMilliseconds(DateTimeOffset dto) - { -#if UNIXTIME - return dto.ToUnixTimeMilliseconds(); -#else - long milliseconds = dto.UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond; - return milliseconds - UnixEpochMilliseconds; -#endif - } + } } diff --git a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs index c9d0d130b..eecebfb24 100644 --- a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs +++ b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs @@ -5,8 +5,8 @@ namespace Discord public static class SnowflakeUtils { public static DateTimeOffset FromSnowflake(ulong value) - => DateTimeUtils.FromUnixMilliseconds((long)((value >> 22) + 1420070400000UL)); + => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); public static ulong ToSnowflake(DateTimeOffset value) - => ((ulong)DateTimeUtils.ToUnixMilliseconds(value) - 1420070400000UL) << 22; + => ((ulong)value.ToUnixTimeMilliseconds() - 1420070400000UL) << 22; } } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 0eb07a4b2..75b69bd04 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -1,20 +1,19 @@ - + Discord.Net.Rest Discord.Rest A core Discord.Net library containing the REST client and models. - net45;netstandard1.1;netstandard1.3 - netstandard1.1;netstandard1.3 + net46;netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0 - - - + + - + diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index dbbabbd72..6084ca4dc 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -168,7 +168,6 @@ namespace Discord.Rest return RestUserMessage.Create(client, channel, client.CurrentUser, model); } -#if FILESYSTEM public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS, Embed embed, RequestOptions options) { @@ -176,7 +175,7 @@ namespace Discord.Rest using (var file = File.OpenRead(filePath)) return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); } -#endif + public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index b6f891f40..e0095c7b1 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -8,10 +8,9 @@ namespace Discord.Rest { /// Sends a message to this message channel. new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#if FILESYSTEM /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#endif + /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 21cd579e6..64efcf24b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -65,10 +65,10 @@ namespace Discord.Rest public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -126,10 +126,9 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 901016a6b..367abfb4a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -83,10 +83,10 @@ namespace Discord.Rest public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -136,10 +136,9 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 841aad666..b01a88210 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -60,10 +60,10 @@ namespace Discord.Rest public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -131,10 +131,9 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 8f69388d4..e8b939e65 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -35,11 +35,10 @@ namespace Discord.Rest public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) @@ -86,10 +85,9 @@ namespace Discord.Rest async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options); -#if FILESYSTEM async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index 9213c5d75..8a3c1037b 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -1,4 +1,4 @@ -using Discord.API; +using Discord.API; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; @@ -25,7 +25,6 @@ namespace Discord.Net.Converters if (converter != null) { property.Converter = converter; - property.MemberConverter = converter; } } else diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 3346681b5..c4f5996c5 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -230,7 +230,7 @@ namespace Discord.Net.Queue #endif } - var now = DateTimeUtils.ToUnixSeconds(DateTimeOffset.UtcNow); + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); DateTimeOffset? resetTick = null; //Using X-RateLimit-Remaining causes a race condition diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index 9421221ed..a517f290c 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Discord.Net @@ -21,7 +21,7 @@ namespace Discord.Net Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && int.TryParse(temp, out var remaining) ? remaining : (int?)null; Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) && - int.TryParse(temp, out var reset) ? DateTimeUtils.FromUnixSeconds(reset) : (DateTimeOffset?)null; + int.TryParse(temp, out var reset) ? DateTimeOffset.FromUnixTimeSeconds(reset) : (DateTimeOffset?)null; RetryAfter = headers.TryGetValue("Retry-After", out temp) && int.TryParse(temp, out var retryAfter) ? retryAfter : (int?)null; Lag = headers.TryGetValue("Date", out temp) && diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index 251cdb18b..ddd3b7954 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -1,11 +1,11 @@ - + Discord.Net.WebSocket Discord.WebSocket A core Discord.Net library containing the WebSocket client and models. - net45;netstandard1.1;netstandard1.3 - netstandard1.1;netstandard1.3 + net46;netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0 true @@ -13,6 +13,6 @@ - + diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 72b0b022b..01322a3cc 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -365,7 +365,7 @@ namespace Discord.WebSocket await ApiClient.SendStatusUpdateAsync( status, status == UserStatus.AFK, - statusSince != null ? DateTimeUtils.ToUnixMilliseconds(_statusSince.Value) : (long?)null, + statusSince != null ? _statusSince.Value.ToUnixTimeMilliseconds() : (long?)null, gameModel).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 25dc2cf7b..0eb92caed 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Discord.API; using Discord.API.Voice; using Discord.Net.Converters; @@ -129,7 +129,7 @@ namespace Discord.Audio //WebSocket public async Task SendHeartbeatAsync(RequestOptions options = null) { - await SendAsync(VoiceOpCode.Heartbeat, DateTimeUtils.ToUnixMilliseconds(DateTimeOffset.UtcNow), options: options).ConfigureAwait(false); + await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false); } public async Task SendIdentityAsync(ulong userId, string sessionId, string token) { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 971dbe8f1..5fef7e4cd 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -12,10 +12,9 @@ namespace Discord.WebSocket /// Sends a message to this message channel. new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#if FILESYSTEM /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); -#endif + /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index d95d45890..11b7ce25a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -69,10 +69,10 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -135,10 +135,8 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 3d6f60db2..81b5cfbf3 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -97,10 +97,10 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -199,10 +199,10 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 9373c431a..dc68044fe 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -77,10 +77,10 @@ namespace Discord.WebSocket public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); -#if FILESYSTEM + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); -#endif + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); @@ -159,10 +159,10 @@ namespace Discord.WebSocket => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); -#if FILESYSTEM + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); -#endif + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs index 6a6194397..251a761d4 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs @@ -1,4 +1,3 @@ -#if DEFAULTUDPCLIENT using System; using System.Net; using System.Net.Sockets; @@ -85,11 +84,7 @@ namespace Discord.Net.Udp if (_udp != null) { -#if UDPDISPOSE try { _udp.Dispose(); } -#else - try { _udp.Close(); } -#endif catch { } _udp = null; } @@ -132,4 +127,3 @@ namespace Discord.Net.Udp } } } -#endif \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs index 82b6ec4c0..d701fa79a 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs @@ -4,7 +4,6 @@ namespace Discord.Net.Udp { public static class DefaultUdpSocketProvider { -#if DEFAULTUDPCLIENT public static readonly UdpSocketProvider Instance = () => { try @@ -16,12 +15,5 @@ namespace Discord.Net.Udp throw new PlatformNotSupportedException("The default UdpSocketProvider is not supported on this platform.", ex); } }; -#else - public static readonly UdpSocketProvider Instance = () => - { - throw new PlatformNotSupportedException("The default UdpSocketProvider is not supported on this platform.\n" + - "You must specify a UdpSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); - }; -#endif } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index a250acec9..c60368da0 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -1,4 +1,3 @@ -#if DEFAULTWEBSOCKET using System; using System.Collections.Generic; using System.ComponentModel; @@ -209,14 +208,9 @@ namespace Discord.Net.WebSockets //Use the internal buffer if we can get it resultCount = (int)stream.Length; -#if MSTRYBUFFER - if (stream.TryGetBuffer(out var streamBuffer)) - result = streamBuffer.Array; - else - result = stream.ToArray(); -#else - result = stream.GetBuffer(); -#endif + + result = stream.TryGetBuffer(out var streamBuffer) ? streamBuffer.Array : stream.ToArray(); + } } else @@ -248,4 +242,3 @@ namespace Discord.Net.WebSockets } } } -#endif \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs index 68bd67c5b..2d66d5900 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs @@ -5,7 +5,6 @@ namespace Discord.Net.WebSockets { public static class DefaultWebSocketProvider { -#if DEFAULTWEBSOCKET public static readonly WebSocketProvider Instance = Create(); public static WebSocketProvider Create(IWebProxy proxy = null) @@ -22,12 +21,5 @@ namespace Discord.Net.WebSockets } }; } -#else - public static readonly WebSocketProvider Instance = () => - { - throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.\n" + - "You must specify a WebSocketProvider or target a runtime supporting .NET Standard 1.3, such as .NET Framework 4.6+."); - }; -#endif } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj index 7c224e01e..ba7bbcff8 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj @@ -1,10 +1,10 @@ - + Discord.Net.Webhook Discord.Webhook A core Discord.Net library containing the Webhook client and models. - netstandard1.1 + netstandard1.3 diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 2dea1c1d1..67a5462be 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -67,12 +67,11 @@ namespace Discord.Webhook string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, options); -#if FILESYSTEM /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. public Task SendFileAsync(string filePath, string text, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) => WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl, options); -#endif + /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 1116662a6..d3cac9703 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -33,7 +33,6 @@ namespace Discord.Webhook var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; } -#if FILESYSTEM public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, RequestOptions options) { @@ -41,7 +40,6 @@ namespace Discord.Webhook using (var file = File.OpenRead(filePath)) return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, options).ConfigureAwait(false); } -#endif public static async Task SendFileAsync(DiscordWebhookClient client, Stream stream, string filename, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, RequestOptions options) { diff --git a/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/test/Discord.Net.Tests/Discord.Net.Tests.csproj index 204dca5c4..60491a96f 100644 --- a/test/Discord.Net.Tests/Discord.Net.Tests.csproj +++ b/test/Discord.Net.Tests/Discord.Net.Tests.csproj @@ -21,10 +21,10 @@ - - - - - + + + + + diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index b37a1195e..da969b320 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -84,7 +84,7 @@ namespace Discord Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue); return Task.CompletedTask; } - + [Fact] public Task TestChannelPermissionModify() { // test channel permission modify diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index b528ca5fb..cd629faa4 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -49,35 +49,35 @@ namespace Discord } private static void CheckTextChannels(RestGuild guild, params RestTextChannel[] textChannels) { - Assert.Equal(textChannels.Length, 5); + Assert.Equal(5, textChannels.Length); Assert.All(textChannels, x => { Assert.NotNull(x); - Assert.NotEqual(x.Id, 0UL); + Assert.NotEqual(0UL, x.Id); Assert.True(x.Position >= 0); }); - var text1 = textChannels.Where(x => x.Name == "text1").FirstOrDefault(); - var text2 = textChannels.Where(x => x.Name == "text2").FirstOrDefault(); - var text3 = textChannels.Where(x => x.Name == "text3").FirstOrDefault(); - var text4 = textChannels.Where(x => x.Name == "text4").FirstOrDefault(); - var text5 = textChannels.Where(x => x.Name == "text5").FirstOrDefault(); + var text1 = textChannels.FirstOrDefault(x => x.Name == "text1"); + var text2 = textChannels.FirstOrDefault(x => x.Name == "text2"); + var text3 = textChannels.FirstOrDefault(x => x.Name == "text3"); + var text4 = textChannels.FirstOrDefault(x => x.Name == "text4"); + var text5 = textChannels.FirstOrDefault(x => x.Name == "text5"); Assert.NotNull(text1); //Assert.True(text1.Id == guild.DefaultChannelId); - Assert.Equal(text1.Position, 1); - Assert.Equal(text1.Topic, "Topic1"); + Assert.Equal(1, text1.Position); + Assert.Equal("Topic1", text1.Topic); Assert.NotNull(text2); - Assert.Equal(text2.Position, 2); + Assert.Equal(2, text2.Position); Assert.Null(text2.Topic); Assert.NotNull(text3); - Assert.Equal(text3.Topic, "Topic2"); + Assert.Equal("Topic2", text3.Topic); Assert.NotNull(text4); - Assert.Equal(text4.Position, 3); - Assert.Equal(text4.Topic, "Topic2"); + Assert.Equal(3, text4.Position); + Assert.Equal("Topic2", text4.Topic); Assert.NotNull(text5); Assert.Null(text5.Topic); @@ -114,31 +114,31 @@ namespace Discord } private static void CheckVoiceChannels(params RestVoiceChannel[] voiceChannels) { - Assert.Equal(voiceChannels.Length, 3); + Assert.Equal(3, voiceChannels.Length); Assert.All(voiceChannels, x => { Assert.NotNull(x); - Assert.NotEqual(x.Id, 0UL); - Assert.NotEqual(x.UserLimit, 0); + Assert.NotEqual(0UL, x.Id); + Assert.NotEqual(0, x.UserLimit); Assert.True(x.Bitrate > 0); Assert.True(x.Position >= 0); }); - var voice1 = voiceChannels.Where(x => x.Name == "voice1").FirstOrDefault(); - var voice2 = voiceChannels.Where(x => x.Name == "voice2").FirstOrDefault(); - var voice3 = voiceChannels.Where(x => x.Name == "voice3").FirstOrDefault(); + var voice1 = voiceChannels.FirstOrDefault(x => x.Name == "voice1"); + var voice2 = voiceChannels.FirstOrDefault(x => x.Name == "voice2"); + var voice3 = voiceChannels.FirstOrDefault(x => x.Name == "voice3"); Assert.NotNull(voice1); - Assert.Equal(voice1.Bitrate, 96000); - Assert.Equal(voice1.Position, 1); + Assert.Equal(96000, voice1.Bitrate); + Assert.Equal(1, voice1.Position); Assert.NotNull(voice2); - Assert.Equal(voice2.UserLimit, null); + Assert.Null(voice2.UserLimit); Assert.NotNull(voice3); - Assert.Equal(voice3.Bitrate, 8000); - Assert.Equal(voice3.Position, 1); - Assert.Equal(voice3.UserLimit, 16); + Assert.Equal(8000, voice3.Bitrate); + Assert.Equal(1, voice3.Position); + Assert.Equal(16, voice3.UserLimit); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Tests.Colors.cs b/test/Discord.Net.Tests/Tests.Colors.cs index 591778972..10b0bbdac 100644 --- a/test/Discord.Net.Tests/Tests.Colors.cs +++ b/test/Discord.Net.Tests/Tests.Colors.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; namespace Discord @@ -12,6 +12,7 @@ namespace Discord Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue); Assert.Equal(uint.MaxValue, new Color(uint.MaxValue).RawValue); } + [Fact] public void Color_Default() { Assert.Equal(0u, Color.Default.RawValue); diff --git a/test/Discord.Net.Tests/Tests.Emotes.cs b/test/Discord.Net.Tests/Tests.Emotes.cs index 334975ce4..eeadbddf8 100644 --- a/test/Discord.Net.Tests/Tests.Emotes.cs +++ b/test/Discord.Net.Tests/Tests.Emotes.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; namespace Discord @@ -34,6 +34,7 @@ namespace Discord Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt); Assert.EndsWith("gif", emote.Url); } + [Fact] public void Test_Invalid_Amimated_Emote_Parse() { Assert.False(Emote.TryParse("", out _)); diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index e22659d15..6e71da1b1 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -25,15 +25,15 @@ namespace Discord // check that toggling the bit works Permissions.UnsetFlag(ref rawValue, flagValue); - Assert.Equal(false, Permissions.GetValue(rawValue, flagValue)); + Assert.False(Permissions.GetValue(rawValue, flagValue)); Permissions.SetFlag(ref rawValue, flagValue); - Assert.Equal(true, Permissions.GetValue(rawValue, flagValue)); + Assert.True(Permissions.GetValue(rawValue, flagValue)); // do the same, but with the SetValue method Permissions.SetValue(ref rawValue, true, flagValue); - Assert.Equal(true, Permissions.GetValue(rawValue, flagValue)); + Assert.True(Permissions.GetValue(rawValue, flagValue)); Permissions.SetValue(ref rawValue, false, flagValue); - Assert.Equal(false, Permissions.GetValue(rawValue, flagValue)); + Assert.False(Permissions.GetValue(rawValue, flagValue)); } /// From fa759a22eac25183eb0fecf6cd0fbbf7f476fa11 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Fri, 25 May 2018 06:38:59 -0700 Subject: [PATCH 036/106] Remove Build Warnings for Obsolete Guild Permission Read Messages (#1067) - Replaces the usages of `ReadMessages` with `ViewChannel` - Renames the read message parameters of `GuildPermissions#Modify` to be view channel as well --- .../Entities/Permissions/GuildPermissions.cs | 12 ++++++------ test/Discord.Net.Tests/Tests.GuildPermissions.cs | 10 +++++----- test/Discord.Net.Tests/Tests.Permissions.cs | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index dcebeb20d..aa914eda1 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -87,7 +87,7 @@ namespace Discord private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, bool? addReactions = null, bool? viewAuditLog = null, - bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, @@ -103,7 +103,7 @@ namespace Discord Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog); - Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); + Permissions.SetValue(ref value, viewChannel, GuildPermission.ViewChannel); Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); Permissions.SetValue(ref value, manageMessages, GuildPermission.ManageMessages); @@ -131,14 +131,14 @@ namespace Discord public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, bool addReactions = false, bool viewAuditLog = false, - bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, + bool viewChannel = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers, administrator: administrator, manageChannels: manageChannels, manageGuild: manageGuild, addReactions: addReactions, - viewAuditLog: viewAuditLog, readMessages: readMessages, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages, + viewAuditLog: viewAuditLog, viewChannel: viewChannel, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages, manageMessages: manageMessages, embedLinks: embedLinks, attachFiles: attachFiles, readMessageHistory: readMessageHistory, mentionEveryone: mentionEveryone, useExternalEmojis: useExternalEmojis, connect: connect, speak: speak, muteMembers: muteMembers, deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, changeNickname: changeNickname, @@ -149,13 +149,13 @@ namespace Discord public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, bool? addReactions = null, bool? viewAuditLog = null, - bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, - viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, + viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index a562f4afb..2b7c39341 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -138,12 +138,12 @@ namespace Discord // individual permission test - perm = perm.Modify(readMessages: true); - Assert.True(perm.ReadMessages); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages); + perm = perm.Modify(viewChannel: true); + Assert.True(perm.ViewChannel); + Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewChannel); - perm = perm.Modify(readMessages: false); - Assert.False(perm.ReadMessages); + perm = perm.Modify(viewChannel: false); + Assert.False(perm.ViewChannel); Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 6e71da1b1..0516337da 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -280,7 +280,7 @@ namespace Discord TestHelper(value, GuildPermission.ManageGuild, false); TestHelper(value, GuildPermission.AddReactions, false); TestHelper(value, GuildPermission.ViewAuditLog, false); - TestHelper(value, GuildPermission.ReadMessages, false); + TestHelper(value, GuildPermission.ViewChannel, false); TestHelper(value, GuildPermission.SendMessages, false); TestHelper(value, GuildPermission.SendTTSMessages, false); TestHelper(value, GuildPermission.ManageMessages, false); @@ -323,7 +323,7 @@ namespace Discord TestHelper(value, GuildPermission.ManageGuild, true); TestHelper(value, GuildPermission.AddReactions, true); TestHelper(value, GuildPermission.ViewAuditLog, true); - TestHelper(value, GuildPermission.ReadMessages, true); + TestHelper(value, GuildPermission.ViewChannel, true); TestHelper(value, GuildPermission.SendMessages, true); TestHelper(value, GuildPermission.SendTTSMessages, true); TestHelper(value, GuildPermission.ManageMessages, true); @@ -367,7 +367,7 @@ namespace Discord TestHelper(value, GuildPermission.ManageGuild, false); TestHelper(value, GuildPermission.AddReactions, false); TestHelper(value, GuildPermission.ViewAuditLog, false); - TestHelper(value, GuildPermission.ReadMessages, false); + TestHelper(value, GuildPermission.ViewChannel, false); TestHelper(value, GuildPermission.SendMessages, true); TestHelper(value, GuildPermission.SendTTSMessages, true); TestHelper(value, GuildPermission.ManageMessages, false); From 4d8764e124cef12af4b0944d185ec22d9683c520 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sat, 26 May 2018 14:11:49 +0100 Subject: [PATCH 037/106] Refactor RoleInfo types into separate classes - Fixes indentation with a few files (looks like a bad copy+paste job) - Renames RoleInfo to RoleEditInfo - Moves RoleInfo from MemberRoleAuditLogData into its own class All of this should improve docs. --- .../DataTypes/ChannelUpdateAuditLogData.cs | 4 ++-- .../DataTypes/MemberRoleAuditLogData.cs | 20 +++--------------- .../AuditLogs/DataTypes/MemberRoleEditInfo.cs | 16 ++++++++++++++ .../DataTypes/RoleCreateAuditLogData.cs | 6 +++--- .../DataTypes/RoleDeleteAuditLogData.cs | 6 +++--- .../AuditLogs/DataTypes/RoleEditInfo.cs | 21 +++++++++++++++++++ .../Entities/AuditLogs/DataTypes/RoleInfo.cs | 21 ------------------- .../DataTypes/RoleUpdateAuditLogData.cs | 10 ++++----- 8 files changed, 53 insertions(+), 51 deletions(-) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs delete mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index f3403138d..491cb5717 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -39,7 +39,7 @@ namespace Discord.Rest } public ulong ChannelId { get; } - public ChannelInfo Before { get; set; } - public ChannelInfo After { get; set; } + public ChannelInfo Before { get; } + public ChannelInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index b0f0a1fe1..3bcbce440 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -9,7 +9,7 @@ namespace Discord.Rest { public class MemberRoleAuditLogData : IAuditLogData { - private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) + private MemberRoleAuditLogData(IReadOnlyCollection roles, IUser target) { Roles = roles; Target = target; @@ -21,7 +21,7 @@ namespace Discord.Rest var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), (model, role) => new { model.ChangedProperty, Role = role }) - .Select(x => new RoleInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) + .Select(x => new MemberRoleEditInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) .ToList(); var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); @@ -30,21 +30,7 @@ namespace Discord.Rest return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); } - public IReadOnlyCollection Roles { get; } + public IReadOnlyCollection Roles { get; } public IUser Target { get; } - - public struct RoleInfo - { - internal RoleInfo(string name, ulong roleId, bool added) - { - Name = name; - RoleId = roleId; - Added = added; - } - - public string Name { get; } - public ulong RoleId { get; } - public bool Added { get; } - } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs new file mode 100644 index 000000000..4838b75c9 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleEditInfo.cs @@ -0,0 +1,16 @@ +namespace Discord.Rest +{ + public struct MemberRoleEditInfo + { + internal MemberRoleEditInfo(string name, ulong roleId, bool added) + { + Name = name; + RoleId = roleId; + Added = added; + } + + public string Name { get; } + public ulong RoleId { get; } + public bool Added { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs index aa951d6e7..dcc1c6ab6 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -7,7 +7,7 @@ namespace Discord.Rest { public class RoleCreateAuditLogData : IAuditLogData { - private RoleCreateAuditLogData(ulong id, RoleInfo props) + private RoleCreateAuditLogData(ulong id, RoleEditInfo props) { RoleId = id; Properties = props; @@ -38,10 +38,10 @@ namespace Discord.Rest permissions = new GuildPermissions(permissionsRaw.Value); return new RoleCreateAuditLogData(entry.TargetId.Value, - new RoleInfo(color, mentionable, hoist, name, permissions)); + new RoleEditInfo(color, mentionable, hoist, name, permissions)); } public ulong RoleId { get; } - public RoleInfo Properties { get; } + public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs index e90d70d4d..263909daf 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -7,7 +7,7 @@ namespace Discord.Rest { public class RoleDeleteAuditLogData : IAuditLogData { - private RoleDeleteAuditLogData(ulong id, RoleInfo props) + private RoleDeleteAuditLogData(ulong id, RoleEditInfo props) { RoleId = id; Properties = props; @@ -38,10 +38,10 @@ namespace Discord.Rest permissions = new GuildPermissions(permissionsRaw.Value); return new RoleDeleteAuditLogData(entry.TargetId.Value, - new RoleInfo(color, mentionable, hoist, name, permissions)); + new RoleEditInfo(color, mentionable, hoist, name, permissions)); } public ulong RoleId { get; } - public RoleInfo Properties { get; } + public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs new file mode 100644 index 000000000..186ea8d11 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleEditInfo.cs @@ -0,0 +1,21 @@ +namespace Discord.Rest +{ + public struct RoleEditInfo + { + internal RoleEditInfo(Color? color, bool? mentionable, bool? hoist, string name, + GuildPermissions? permissions) + { + Color = color; + Mentionable = mentionable; + Hoist = hoist; + Name = name; + Permissions = permissions; + } + + public Color? Color { get; } + public bool? Mentionable { get; } + public bool? Hoist { get; } + public string Name { get; } + public GuildPermissions? Permissions { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs deleted file mode 100644 index 2208990e6..000000000 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Discord.Rest -{ - public struct RoleInfo - { - internal RoleInfo(Color? color, bool? mentionable, bool? hoist, string name, - GuildPermissions? permissions) - { - Color = color; - Mentionable = mentionable; - Hoist = hoist; - Name = name; - Permissions = permissions; - } - - public Color? Color { get; } - public bool? Mentionable { get; } - public bool? Hoist { get; } - public string Name { get; } - public GuildPermissions? Permissions { get; } - } -} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs index be484e2d5..b645ef7ae 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -7,7 +7,7 @@ namespace Discord.Rest { public class RoleUpdateAuditLogData : IAuditLogData { - private RoleUpdateAuditLogData(ulong id, RoleInfo oldProps, RoleInfo newProps) + private RoleUpdateAuditLogData(ulong id, RoleEditInfo oldProps, RoleEditInfo newProps) { RoleId = id; Before = oldProps; @@ -49,14 +49,14 @@ namespace Discord.Rest if (newPermissionsRaw.HasValue) newPermissions = new GuildPermissions(newPermissionsRaw.Value); - var oldProps = new RoleInfo(oldColor, oldMentionable, oldHoist, oldName, oldPermissions); - var newProps = new RoleInfo(newColor, newMentionable, newHoist, newName, newPermissions); + var oldProps = new RoleEditInfo(oldColor, oldMentionable, oldHoist, oldName, oldPermissions); + var newProps = new RoleEditInfo(newColor, newMentionable, newHoist, newName, newPermissions); return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps); } public ulong RoleId { get; } - public RoleInfo Before { get; } - public RoleInfo After { get; } + public RoleEditInfo Before { get; } + public RoleEditInfo After { get; } } } From f9cbff5e42c04aa8a0fe87377ba68aa7d748d873 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Sat, 26 May 2018 11:06:35 -0700 Subject: [PATCH 038/106] Fix #995, Move Category Implementation from IGuildChannel to INestedChannel (#1004) * Fix #995 ICategoryChannel.CategoryID throws NotSupportedException * Add tests * change run mode of TestChannelCategories * Add throw for GetCategoryAsync * Add xml doc explaining why exception is thrown * Add test coverage for text and voice channel categories * initial implementation of INestedChannel * more implementation of INestedChannel design * Add case in RestChannel Create for Category type * set the CategoryID for RestVoiceChannel * rewrite channel category tests to work with existing pattern * remove outdated todo * Make IVoiceChannel implement INestedChannel * remove redundant interface implementation * Add c#7 feature from feedback * Remove redundant GetCategoryAsync methods from socket entities * Added configureawait to async methods * change signature of interface GetCategoryAsync * Add check for cachemode in rest channel GetCategory * remove redundant IGuildChannel interface from ITextChannel and IVoiceChannel --- .../Entities/Channels/ICategoryChannel.cs | 2 +- .../Entities/Channels/IGuildChannel.cs | 8 +- .../Entities/Channels/INestedChannel.cs | 16 ++++ .../Entities/Channels/ITextChannel.cs | 6 +- .../Entities/Channels/IVoiceChannel.cs | 6 +- .../Entities/Channels/ChannelHelper.cs | 10 +++ .../Entities/Channels/RestCategoryChannel.cs | 6 +- .../Entities/Channels/RestChannel.cs | 4 +- .../Entities/Channels/RestGuildChannel.cs | 11 +-- .../Entities/Channels/RestTextChannel.cs | 23 ++++-- .../Entities/Channels/RestVoiceChannel.cs | 16 +++- .../Channels/SocketCategoryChannel.cs | 2 +- .../Entities/Channels/SocketGuildChannel.cs | 13 +--- .../Entities/Channels/SocketTextChannel.cs | 9 ++- .../Entities/Channels/SocketVoiceChannel.cs | 13 +++- test/Discord.Net.Tests/Tests.Channels.cs | 74 +++++++++++++++++++ 16 files changed, 168 insertions(+), 51 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Channels/INestedChannel.cs diff --git a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs index 0f7f5aa62..c004cafd5 100644 --- a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index c9841cb15..6514d46cd 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -9,10 +9,6 @@ namespace Discord /// Gets the position of this channel in the guild's channel list, relative to others of the same type. int Position { get; } - /// Gets the parentid (category) of this channel in the guild's channel list. - ulong? CategoryId { get; } - /// Gets the parent channel (category) of this channel. - Task GetCategoryAsync(); /// Gets the guild this channel is a member of. IGuild Guild { get; } /// Gets the id of the guild this channel is a member of. @@ -49,4 +45,4 @@ namespace Discord /// Gets a user in this channel with the provided id. new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs new file mode 100644 index 000000000..c8d2bcaaf --- /dev/null +++ b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A type of guild channel that can be nested within a category. + /// Contains a CategoryId that is set to the parent category, if it is set. + /// + public interface INestedChannel : IGuildChannel + { + /// Gets the parentid (category) of this channel in the guild's channel list. + ulong? CategoryId { get; } + /// Gets the parent channel (category) of this channel, if it is set. If unset, returns null. + Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + } +} diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 7c6ec3908..2aa070b03 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; namespace Discord { - public interface ITextChannel : IMessageChannel, IMentionable, IGuildChannel + public interface ITextChannel : IMessageChannel, IMentionable, INestedChannel { /// Checks if the channel is NSFW. bool IsNsfw { get; } @@ -28,4 +28,4 @@ namespace Discord /// Gets the webhooks for this text channel. Task> GetWebhooksAsync(RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index e2a2ad8eb..2e345bfda 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord { - public interface IVoiceChannel : IGuildChannel, IAudioChannel + public interface IVoiceChannel : INestedChannel, IAudioChannel { /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. int Bitrate { get; } @@ -13,4 +13,4 @@ namespace Discord /// Modifies this voice channel. Task ModifyAsync(Action func, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 6084ca4dc..4047b7014 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -316,6 +316,16 @@ namespace Discord.Rest return models.Select(x => RestWebhook.Create(client, channel, x)) .ToImmutableArray(); } + // Categories + public static async Task GetCategoryAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) + { + // if no category id specified, return null + if (!channel.CategoryId.HasValue) + return null; + // CategoryId will contain a value here + var model = await client.ApiClient.GetChannelAsync(channel.CategoryId.Value, options).ConfigureAwait(false); + return RestCategoryChannel.Create(client, model) as ICategoryChannel; + } //Helpers private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model, ulong? webhookId) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs index 397e14e76..321f1f1d2 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -25,10 +25,6 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Category)"; // IGuildChannel - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => throw new NotSupportedException(); Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) => throw new NotSupportedException(); Task> IGuildChannel.GetInvitesAsync(RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 04cc5a937..5860d8283 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -24,6 +24,8 @@ namespace Discord.Rest case ChannelType.DM: case ChannelType.Group: return CreatePrivate(discord, model) as RestChannel; + case ChannelType.Category: + return RestCategoryChannel.Create(discord, new RestGuild(discord, model.GuildId.Value), model); default: return new RestChannel(discord, model.Id); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 026d03cc8..7355e3673 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -16,7 +16,6 @@ namespace Discord.Rest internal IGuild Guild { get; } public string Name { get; private set; } public int Position { get; private set; } - public ulong? CategoryId { get; private set; } public ulong GuildId => Guild.Id; internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) @@ -35,7 +34,6 @@ namespace Discord.Rest case ChannelType.Category: return RestCategoryChannel.Create(discord, guild, model); default: - // TODO: Channel categories return new RestGuildChannel(discord, guild, model.Id); } } @@ -64,13 +62,6 @@ namespace Discord.Rest public Task DeleteAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); - public async Task GetCategoryAsync() - { - if (CategoryId.HasValue) - return (await Guild.GetChannelAsync(CategoryId.Value).ConfigureAwait(false)) as ICategoryChannel; - return null; - } - public OverwritePermissions? GetPermissionOverwrite(IUser user) { for (int i = 0; i < _overwrites.Length; i++) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index b01a88210..a08585cd8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -12,6 +12,7 @@ namespace Discord.Rest public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } + public ulong? CategoryId { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); @@ -31,7 +32,7 @@ namespace Discord.Rest internal override void Update(Model model) { base.Update(model); - + CategoryId = model.CategoryId; Topic = model.Topic.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } @@ -46,7 +47,7 @@ namespace Discord.Rest => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); - + public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -88,16 +89,19 @@ namespace Discord.Rest => ChannelHelper.GetWebhookAsync(this, Discord, id, options); public Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); + + public Task GetCategoryAsync(RequestOptions options = null) + => ChannelHelper.GetCategoryAsync(this, Discord, options); private string DebuggerDisplay => $"{Name} ({Id}, Text)"; //ITextChannel async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) - => await CreateWebhookAsync(name, avatar, options); + => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); + => await GetWebhookAsync(id, options).ConfigureAwait(false); async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); + => await GetWebhooksAsync(options).ConfigureAwait(false); //IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) @@ -114,6 +118,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) @@ -172,5 +177,13 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + + // INestedChannel + async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) + { + if (CategoryId.HasValue && mode == CacheMode.AllowDownload) + return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; + return null; + } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 300ebd08d..a2bead45f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,6 +13,7 @@ namespace Discord.Rest { public int Bitrate { get; private set; } public int? UserLimit { get; private set; } + public ulong? CategoryId { get; private set; } internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) @@ -27,7 +28,7 @@ namespace Discord.Rest internal override void Update(Model model) { base.Update(model); - + CategoryId = model.CategoryId; Bitrate = model.Bitrate.Value; UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } @@ -38,6 +39,9 @@ namespace Discord.Rest Update(model); } + public Task GetCategoryAsync(RequestOptions options = null) + => ChannelHelper.GetCategoryAsync(this, Discord, options); + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; //IAudioChannel @@ -48,5 +52,13 @@ namespace Discord.Rest => Task.FromResult(null); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => AsyncEnumerable.Empty>(); + + // INestedChannel + async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) + { + if (CategoryId.HasValue && mode == CacheMode.AllowDownload) + return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; + return null; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index e7a165c2f..74ca02dba 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -20,7 +20,7 @@ namespace Discord.WebSocket ChannelPermission.ViewChannel)).ToImmutableArray(); public IReadOnlyCollection Channels - => Guild.Channels.Where(x => x.CategoryId == Id).ToImmutableArray(); + => Guild.Channels.Where(x => x is INestedChannel nestedChannel && nestedChannel.CategoryId == Id).ToImmutableArray(); internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 2163daf55..bfcffa35f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -1,4 +1,4 @@ -using Discord.Rest; +using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -16,10 +16,7 @@ namespace Discord.WebSocket public SocketGuild Guild { get; } public string Name { get; private set; } - public int Position { get; private set; } - public ulong? CategoryId { get; private set; } - public ICategoryChannel Category - => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; + public int Position { get; private set; } public IReadOnlyCollection PermissionOverwrites => _overwrites; public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); @@ -48,8 +45,7 @@ namespace Discord.WebSocket { Name = model.Name.Value; Position = model.Position.Value; - CategoryId = model.CategoryId; - + var overwrites = model.PermissionOverwrites.Value; var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); for (int i = 0; i < overwrites.Length; i++) @@ -135,9 +131,6 @@ namespace Discord.WebSocket IGuild IGuildChannel.Guild => Guild; ulong IGuildChannel.GuildId => Guild.Id; - Task IGuildChannel.GetCategoryAsync() - => Task.FromResult(Category); - async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index dc68044fe..1d8041585 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,6 +16,9 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } + public ulong? CategoryId { get; private set; } + public ICategoryChannel Category + => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; private bool _nsfw; public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); @@ -42,7 +45,7 @@ namespace Discord.WebSocket internal override void Update(ClientState state, Model model) { base.Update(state, model); - + CategoryId = model.CategoryId; Topic = model.Topic.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } @@ -169,5 +172,9 @@ namespace Discord.WebSocket => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); + + // INestedChannel + Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(Category); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index e8a669845..349621fac 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using Discord.Rest; using System; using System.Collections.Generic; @@ -15,6 +15,9 @@ namespace Discord.WebSocket { public int Bitrate { get; private set; } public int? UserLimit { get; private set; } + public ulong? CategoryId { get; private set; } + public ICategoryChannel Category + => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; public override IReadOnlyCollection Users => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); @@ -32,7 +35,7 @@ namespace Discord.WebSocket internal override void Update(ClientState state, Model model) { base.Update(state, model); - + CategoryId = model.CategoryId; Bitrate = model.Bitrate.Value; UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } @@ -52,7 +55,7 @@ namespace Discord.WebSocket return user; return null; } - + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; @@ -61,5 +64,9 @@ namespace Discord.WebSocket => Task.FromResult(GetUser(id)); IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + // INestedChannel + Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(Category); } } diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index cd629faa4..2c189c03f 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -1,4 +1,5 @@ using Discord.Rest; +using System; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -15,17 +16,28 @@ namespace Discord var text4 = await guild.CreateTextChannelAsync("text4"); var text5 = await guild.CreateTextChannelAsync("text5"); + // create a channel category + var cat1 = await guild.CreateCategoryChannelAsync("cat1"); + + if (text1 == null) + { + // the guild did not have a default channel, so make a new one + text1 = await guild.CreateTextChannelAsync("default"); + } + //Modify #general await text1.ModifyAsync(x => { x.Name = "text1"; x.Position = 1; x.Topic = "Topic1"; + x.CategoryId = cat1.Id; }); await text2.ModifyAsync(x => { x.Position = 2; + x.CategoryId = cat1.Id; }); await text3.ModifyAsync(x => { @@ -89,10 +101,13 @@ namespace Discord var voice2 = await guild.CreateVoiceChannelAsync("voice2"); var voice3 = await guild.CreateVoiceChannelAsync("voice3"); + var cat2 = await guild.CreateCategoryChannelAsync("cat2"); + await voice1.ModifyAsync(x => { x.Bitrate = 96000; x.Position = 1; + x.CategoryId = cat2.Id; }); await voice2.ModifyAsync(x => { @@ -103,6 +118,7 @@ namespace Discord x.Bitrate = 8000; x.Position = 1; x.UserLimit = 16; + x.CategoryId = cat2.Id; }); CheckVoiceChannels(voice1, voice2, voice3); @@ -140,5 +156,63 @@ namespace Discord Assert.Equal(1, voice3.Position); Assert.Equal(16, voice3.UserLimit); } + + [Fact] + public async Task TestChannelCategories() + { + // (await _guild.GetVoiceChannelsAsync()).ToArray() + var channels = await _guild.GetCategoryChannelsAsync(); + + await CheckChannelCategories(channels.ToArray(), (await _guild.GetChannelsAsync()).ToArray()); + } + + private async Task CheckChannelCategories(RestCategoryChannel[] categories, RestGuildChannel[] allChannels) + { + // 2 categories + Assert.Equal(categories.Length, 2); + + var cat1 = categories.Where(x => x.Name == "cat1").FirstOrDefault(); + var cat2 = categories.Where(x => x.Name == "cat2").FirstOrDefault(); + + Assert.NotNull(cat1); + Assert.NotNull(cat2); + + // get text1, text2, ensure they have category id == cat1 + var text1 = allChannels.Where(x => x.Name == "text1").FirstOrDefault() as RestTextChannel; + var text2 = allChannels.Where(x => x.Name == "text2").FirstOrDefault() as RestTextChannel; + + Assert.NotNull(text1); + Assert.NotNull(text2); + + // check that CategoryID and .GetCategoryAsync work correctly + // for both of the text channels + Assert.Equal(text1.CategoryId, cat1.Id); + var text1Cat = await text1.GetCategoryAsync(); + Assert.Equal(text1Cat.Id, cat1.Id); + Assert.Equal(text1Cat.Name, cat1.Name); + + Assert.Equal(text2.CategoryId, cat1.Id); + var text2Cat = await text2.GetCategoryAsync(); + Assert.Equal(text2Cat.Id, cat1.Id); + Assert.Equal(text2Cat.Name, cat1.Name); + + // do the same for the voice channels + var voice1 = allChannels.Where(x => x.Name == "voice1").FirstOrDefault() as RestVoiceChannel; + var voice3 = allChannels.Where(x => x.Name == "voice3").FirstOrDefault() as RestVoiceChannel; + + Assert.NotNull(voice1); + Assert.NotNull(voice3); + + Assert.Equal(voice1.CategoryId, cat2.Id); + var voice1Cat = await voice1.GetCategoryAsync(); + Assert.Equal(voice1Cat.Id, cat2.Id); + Assert.Equal(voice1Cat.Name, cat2.Name); + + Assert.Equal(voice3.CategoryId, cat2.Id); + var voice3Cat = await voice3.GetCategoryAsync(); + Assert.Equal(voice3Cat.Id, cat2.Id); + Assert.Equal(voice3Cat.Name, cat2.Name); + + } } } From a06e21261c66e7aeacde11911ce5ee1ed6baba2f Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Sat, 26 May 2018 11:15:09 -0700 Subject: [PATCH 039/106] Fix ChannelPermissions Modify parameter to be correct default value (#1003) * fix channel permissions modify parameter to use nullable boolean, correct default value * Add general tests for the ChannelPermissions.Modify method to test default values * remove unused cast in tests * add guildpermission modify no param tests * Add no-param modify tests for OverwritePermissions * fix inconsistent parameters in GuildPermissions cstr * Adjust formatting of methods and cstrs with many parameters * remove temp file that was included. no idea what that is * Fix System dependency I should really stop fixing merge conflicts in the github website. --- .../Permissions/ChannelPermissions.cs | 99 +++++++++--- .../Entities/Permissions/GuildPermissions.cs | 147 ++++++++++++++---- .../Permissions/OverwritePermissions.cs | 76 +++++++-- .../Tests.ChannelPermissions.cs | 21 +++ .../Tests.GuildPermissions.cs | 12 ++ test/Discord.Net.Tests/Tests.Permissions.cs | 66 ++++++++ 6 files changed, 356 insertions(+), 65 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 038b18e2e..fa2dfb576 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -87,12 +87,27 @@ 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, + private ChannelPermissions(ulong initialValue, + bool? createInstantInvite = null, + bool? manageChannel = null, bool? addReactions = null, - bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) + bool? viewChannel = null, + bool? sendMessages = null, + bool? sendTTSMessages = null, + bool? manageMessages = null, + bool? embedLinks = null, + bool? attachFiles = null, + bool? readMessageHistory = null, + bool? mentionEveryone = null, + bool? useExternalEmojis = null, + bool? connect = null, + bool? speak = null, + bool? muteMembers = null, + bool? deafenMembers = null, + bool? moveMembers = null, + bool? useVoiceActivation = null, + bool? manageRoles = null, + bool? manageWebhooks = null) { ulong value = initialValue; @@ -121,27 +136,75 @@ namespace Discord } /// Creates a new ChannelPermissions with the provided permissions. - public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, + public ChannelPermissions( + bool createInstantInvite = false, + bool manageChannel = false, bool addReactions = false, - bool viewChannel = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, - bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, - bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, - bool moveMembers = false, bool useVoiceActivation = false, bool manageRoles = false, bool manageWebhooks = false) + bool viewChannel = false, + bool sendMessages = false, + bool sendTTSMessages = false, + bool manageMessages = false, + bool embedLinks = false, + bool attachFiles = false, + bool readMessageHistory = false, + bool mentionEveryone = false, + bool useExternalEmojis = false, + bool connect = false, + bool speak = false, + bool muteMembers = false, + bool deafenMembers = false, + bool moveMembers = false, + bool useVoiceActivation = false, + bool manageRoles = false, + bool manageWebhooks = false) : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. - public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, + public ChannelPermissions Modify( + bool? createInstantInvite = null, + bool? manageChannel = null, bool? addReactions = null, - bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) - => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, - embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); + bool? viewChannel = null, + bool? sendMessages = null, + bool? sendTTSMessages = null, + bool? manageMessages = null, + bool? embedLinks = null, + bool? attachFiles = null, + bool? readMessageHistory = null, + bool? mentionEveryone = null, + bool? useExternalEmojis = null, + bool? connect = null, + bool? speak = null, + bool? muteMembers = null, + bool? deafenMembers = null, + bool? moveMembers = null, + bool? useVoiceActivation = null, + bool? manageRoles = null, + bool? manageWebhooks = null) + => new ChannelPermissions(RawValue, + createInstantInvite, + manageChannel, + addReactions, + viewChannel, + sendMessages, + sendTTSMessages, + manageMessages, + embedLinks, + attachFiles, + readMessageHistory, + mentionEveryone, + useExternalEmojis, + connect, + speak, + muteMembers, + deafenMembers, + moveMembers, + useVoiceActivation, + manageRoles, + manageWebhooks); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index aa914eda1..7704a62d6 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -84,14 +84,35 @@ namespace Discord /// Creates a new GuildPermissions with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } - private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, - bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, - bool? addReactions = null, bool? viewAuditLog = null, - bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, - bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) + private GuildPermissions(ulong initialValue, + bool? createInstantInvite = null, + bool? kickMembers = null, + bool? banMembers = null, + bool? administrator = null, + bool? manageChannels = null, + bool? manageGuild = null, + bool? addReactions = null, + bool? viewAuditLog = null, + bool? viewChannel = null, + bool? sendMessages = null, + bool? sendTTSMessages = null, + bool? manageMessages = null, + bool? embedLinks = null, + bool? attachFiles = null, + bool? readMessageHistory = null, + bool? mentionEveryone = null, + bool? useExternalEmojis = null, + bool? connect = null, + bool? speak = null, + bool? muteMembers = null, + bool? deafenMembers = null, + bool? moveMembers = null, + bool? useVoiceActivation = null, + bool? changeNickname = null, + bool? manageNicknames = null, + bool? manageRoles = null, + bool? manageWebhooks = null, + bool? manageEmojis = null) { ulong value = initialValue; @@ -128,32 +149,96 @@ namespace Discord } /// Creates a new GuildPermissions with the provided permissions. - public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, - bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, - bool addReactions = false, bool viewAuditLog = false, - bool viewChannel = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, - bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, - bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, - bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, - bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) - : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers, - administrator: administrator, manageChannels: manageChannels, manageGuild: manageGuild, addReactions: addReactions, - viewAuditLog: viewAuditLog, viewChannel: viewChannel, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages, - manageMessages: manageMessages, embedLinks: embedLinks, attachFiles: attachFiles, readMessageHistory: readMessageHistory, - mentionEveryone: mentionEveryone, useExternalEmojis: useExternalEmojis, connect: connect, speak: speak, muteMembers: muteMembers, - deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, changeNickname: changeNickname, - manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, manageEmojis: manageEmojis) + public GuildPermissions( + bool createInstantInvite = false, + bool kickMembers = false, + bool banMembers = false, + bool administrator = false, + bool manageChannels = false, + bool manageGuild = false, + bool addReactions = false, + bool viewAuditLog = false, + bool viewChannel = false, + bool sendMessages = false, + bool sendTTSMessages = false, + bool manageMessages = false, + bool embedLinks = false, + bool attachFiles = false, + bool readMessageHistory = false, + bool mentionEveryone = false, + bool useExternalEmojis = false, + bool connect = false, + bool speak = false, + bool muteMembers = false, + bool deafenMembers = false, + bool moveMembers = false, + bool useVoiceActivation = false, + bool changeNickname = false, + bool manageNicknames = false, + bool manageRoles = false, + bool manageWebhooks = false, + bool manageEmojis = false) + : this(0, + createInstantInvite: createInstantInvite, + manageRoles: manageRoles, + kickMembers: kickMembers, + banMembers: banMembers, + administrator: administrator, + manageChannels: manageChannels, + manageGuild: manageGuild, + addReactions: addReactions, + viewAuditLog: viewAuditLog, + viewChannel: viewChannel, + sendMessages: sendMessages, + sendTTSMessages: sendTTSMessages, + manageMessages: manageMessages, + embedLinks: embedLinks, + attachFiles: attachFiles, + readMessageHistory: readMessageHistory, + mentionEveryone: mentionEveryone, + useExternalEmojis: useExternalEmojis, + connect: connect, + speak: speak, + muteMembers: muteMembers, + deafenMembers: deafenMembers, + moveMembers: moveMembers, + useVoiceActivation: useVoiceActivation, + changeNickname: changeNickname, + manageNicknames: manageNicknames, + manageWebhooks: manageWebhooks, + manageEmojis: manageEmojis) { } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. - public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, - bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, - bool? addReactions = null, bool? viewAuditLog = null, - bool? viewChannel = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, - bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, - bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, - bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, - bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) + public GuildPermissions Modify( + bool? createInstantInvite = null, + bool? kickMembers = null, + bool? banMembers = null, + bool? administrator = null, + bool? manageChannels = null, + bool? manageGuild = null, + bool? addReactions = null, + bool? viewAuditLog = null, + bool? viewChannel = null, + bool? sendMessages = null, + bool? sendTTSMessages = null, + bool? manageMessages = null, + bool? embedLinks = null, + bool? attachFiles = null, + bool? readMessageHistory = null, + bool? mentionEveryone = null, + bool? useExternalEmojis = null, + bool? connect = null, + bool? speak = null, + bool? muteMembers = null, + bool? deafenMembers = null, + bool? moveMembers = null, + bool? useVoiceActivation = null, + bool? changeNickname = null, + bool? manageNicknames = null, + bool? manageRoles = null, + bool? manageWebhooks = null, + bool? manageEmojis = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 108b67273..54a4e4035 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; @@ -74,12 +74,26 @@ namespace Discord DenyValue = denyValue; } - private OverwritePermissions(ulong allowValue, ulong denyValue, PermValue? createInstantInvite = null, PermValue? manageChannel = null, + private OverwritePermissions(ulong allowValue, ulong denyValue, + PermValue? createInstantInvite = null, + PermValue? manageChannel = null, PermValue? addReactions = null, - PermValue? viewChannel = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, - PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, - PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, - PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, + PermValue? viewChannel = null, + PermValue? sendMessages = null, + PermValue? sendTTSMessages = null, + PermValue? manageMessages = null, + PermValue? embedLinks = null, + PermValue? attachFiles = null, + PermValue? readMessageHistory = null, + PermValue? mentionEveryone = null, + PermValue? useExternalEmojis = null, + PermValue? connect = null, + PermValue? speak = null, + PermValue? muteMembers = null, + PermValue? deafenMembers = null, + PermValue? moveMembers = null, + PermValue? useVoiceActivation = null, + PermValue? manageRoles = null, PermValue? manageWebhooks = null) { Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); @@ -108,23 +122,53 @@ namespace Discord } /// Creates a new ChannelPermissions with the provided permissions. - public OverwritePermissions(PermValue createInstantInvite = PermValue.Inherit, PermValue manageChannel = PermValue.Inherit, + public OverwritePermissions( + PermValue createInstantInvite = PermValue.Inherit, + PermValue manageChannel = PermValue.Inherit, PermValue addReactions = PermValue.Inherit, - PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, - PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit, - PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit, - PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) + PermValue readMessages = PermValue.Inherit, + PermValue sendMessages = PermValue.Inherit, + PermValue sendTTSMessages = PermValue.Inherit, + PermValue manageMessages = PermValue.Inherit, + PermValue embedLinks = PermValue.Inherit, + PermValue attachFiles = PermValue.Inherit, + PermValue readMessageHistory = PermValue.Inherit, + PermValue mentionEveryone = PermValue.Inherit, + PermValue useExternalEmojis = PermValue.Inherit, + PermValue connect = PermValue.Inherit, + PermValue speak = PermValue.Inherit, + PermValue muteMembers = PermValue.Inherit, + PermValue deafenMembers = PermValue.Inherit, + PermValue moveMembers = PermValue.Inherit, + PermValue useVoiceActivation = PermValue.Inherit, + PermValue manageRoles = PermValue.Inherit, + PermValue manageWebhooks = PermValue.Inherit) : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } /// Creates a new OverwritePermissions from this one, changing the provided non-null permissions. - public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null, + public OverwritePermissions Modify( + PermValue? createInstantInvite = null, + PermValue? manageChannel = null, PermValue? addReactions = 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? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, - PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = 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? useExternalEmojis = null, + PermValue? connect = null, + PermValue? speak = null, + PermValue? muteMembers = null, + PermValue? deafenMembers = null, + PermValue? moveMembers = null, + PermValue? useVoiceActivation = null, + PermValue? manageRoles = null, + PermValue? manageWebhooks = null) => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index da969b320..51834e30d 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -22,6 +22,27 @@ namespace Discord var copy = perm.Modify(); Assert.Equal((ulong)0, copy.RawValue); + // test modify with no parameters after using all + copy = ChannelPermissions.Text; + var modified = copy.Modify(); // no params should not change the result + Assert.Equal(ChannelPermissions.Text.RawValue, modified.RawValue); + + copy = ChannelPermissions.Voice; + modified = copy.Modify(); // no params should not change the result + Assert.Equal(ChannelPermissions.Voice.RawValue, modified.RawValue); + + copy = ChannelPermissions.Group; + modified = copy.Modify(); // no params should not change the result + Assert.Equal(ChannelPermissions.Group.RawValue, modified.RawValue); + + copy = ChannelPermissions.DM; + modified = copy.Modify(); // no params should not change the result + Assert.Equal(ChannelPermissions.DM.RawValue, modified.RawValue); + + copy = new ChannelPermissions(useExternalEmojis: true); + modified = copy.Modify(); + Assert.Equal(copy.RawValue, modified.RawValue); + // test the values that are returned by ChannelPermission.All Assert.Equal((ulong)0, ChannelPermissions.None.RawValue); diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index 2b7c39341..defaf4d02 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -26,6 +26,18 @@ namespace Discord // ensure that the raw values match Assert.Equal((ulong)0, copy.RawValue); + // test modify with no parameters + copy = GuildPermissions.None.Modify(); + Assert.Equal(GuildPermissions.None.RawValue, copy.RawValue); + + // test modify with no paramters on all permissions + copy = GuildPermissions.All.Modify(); + Assert.Equal(GuildPermissions.All.RawValue, copy.RawValue); + + // test modify with no paramters on webhook permissions + copy = GuildPermissions.Webhook.Modify(); + Assert.Equal(GuildPermissions.Webhook.RawValue, copy.RawValue); + // test GuildPermissions.All ulong sumOfAllGuildPermissions = 0; foreach(var v in Enum.GetValues(typeof(GuildPermission))) diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 0516337da..5a7b5a16b 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -702,5 +702,71 @@ namespace Discord return Task.CompletedTask; } + + /// + /// Tests for the + /// method to ensure that the default no-param call does not modify the resulting value + /// of the OverwritePermissions. + /// + /// + public Task TestOverwritePermissionModifyNoParam() + { + // test for all Text allowed, none denied + var original = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, text denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // category allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Category.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, category denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Category.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // DM allowed, none denied + original = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, DM denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // voice allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Voice.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, voice denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Voice.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // group allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, group denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + // none allowed, none denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + + return Task.CompletedTask; + } } } From 8fb2c71814fad9bcab1888fb8d66d693cc98a4b1 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 27 May 2018 16:37:17 -0400 Subject: [PATCH 040/106] Add new member objects to events --- src/Discord.Net.Rest/API/Common/Message.cs | 8 +++++++- src/Discord.Net.Rest/API/Common/VoiceState.cs | 5 ++++- .../API/Gateway/TypingStartEvent.cs | 6 +++++- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 10 ++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 9a7629b96..229249ccf 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -12,10 +12,16 @@ namespace Discord.API public MessageType Type { get; set; } [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + // ALWAYS sent on WebSocket messages + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } [JsonProperty("webhook_id")] public Optional WebhookId { get; set; } [JsonProperty("author")] public Optional Author { get; set; } + // ALWAYS sent on WebSocket messages + [JsonProperty("member")] + public Optional Member { get; set; } [JsonProperty("content")] public Optional Content { get; set; } [JsonProperty("timestamp")] diff --git a/src/Discord.Net.Rest/API/Common/VoiceState.cs b/src/Discord.Net.Rest/API/Common/VoiceState.cs index 563a5f95b..b1f937b09 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceState.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceState.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -11,6 +11,9 @@ namespace Discord.API public ulong? ChannelId { get; set; } [JsonProperty("user_id")] public ulong UserId { get; set; } + // ALWAYS sent over WebSocket, never on REST + [JsonProperty("member")] + public Optional Member { get; set; } [JsonProperty("session_id")] public string SessionId { get; set; } [JsonProperty("deaf")] diff --git a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs index 3cce512bd..5ceae4b7a 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Gateway @@ -9,6 +9,10 @@ namespace Discord.API.Gateway public ulong UserId { get; set; } [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + [JsonProperty("member")] + public GuildMember Member { get; set; } [JsonProperty("timestamp")] public int Timestamp { get; set; } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 01322a3cc..d66252835 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1091,7 +1091,7 @@ namespace Discord.WebSocket if (author == null) { if (guild != null) - author = guild.AddOrUpdateUser(data.Author.Value); //User has no guild-specific data + author = guild.AddOrUpdateUser(data.Member.Value); //per g250k, we can create an entire member now else if (channel is SocketGroupChannel) author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); else @@ -1361,6 +1361,11 @@ namespace Discord.WebSocket } var user = (channel as SocketChannel).GetUser(data.UserId); + if (user == null) + { + if (guild != null) + user = guild.AddOrUpdateUser(data.Member); + } if (user != null) await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); } @@ -1427,7 +1432,8 @@ namespace Discord.WebSocket user = guild.GetUser(data.UserId); if (user == null) { - await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); + user = guild.AddOrUpdateUser(data.Member.Value); //per g250k, this is always sent + //await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); return; } } From c0c565fd7ee03fa569027fff7f17db90d9376b4f Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 27 May 2018 16:45:54 -0400 Subject: [PATCH 041/106] retain fallback case for if user is still null --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index d66252835..2dccd9321 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1429,11 +1429,10 @@ namespace Discord.WebSocket after = SocketVoiceState.Create(null, data); } - user = guild.GetUser(data.UserId); + user = guild.GetUser(data.UserId) ?? guild.AddOrUpdateUser(data.Member.Value); //per g250k, this is always sent if (user == null) { - user = guild.AddOrUpdateUser(data.Member.Value); //per g250k, this is always sent - //await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); + await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); return; } } From adf4da19fc116759c4246c978143987712fa2116 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 27 May 2018 17:56:14 -0400 Subject: [PATCH 042/106] fix: Properly rethrow exceptions in SocketGuild audio client --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 14263f0af..9a9f46baa 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -549,10 +549,10 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); } - catch (Exception) + catch (Exception e) { await DisconnectAudioInternalAsync().ConfigureAwait(false); - throw; + throw e; } finally { @@ -566,10 +566,10 @@ namespace Discord.WebSocket throw new TimeoutException(); return await promise.Task.ConfigureAwait(false); } - catch (Exception) + catch (Exception e) { await DisconnectAudioAsync().ConfigureAwait(false); - throw; + throw e; } } From 237ad0f8675023478ec8ad41c7b6b6fd19ceed2d Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 27 May 2018 18:00:51 -0400 Subject: [PATCH 043/106] Revert "fix: Properly rethrow exceptions in SocketGuild audio client" This reverts commit adf4da19fc116759c4246c978143987712fa2116. Someone hasn't written c# in a while --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 9a9f46baa..14263f0af 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -549,10 +549,10 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); } - catch (Exception e) + catch (Exception) { await DisconnectAudioInternalAsync().ConfigureAwait(false); - throw e; + throw; } finally { @@ -566,10 +566,10 @@ namespace Discord.WebSocket throw new TimeoutException(); return await promise.Task.ConfigureAwait(false); } - catch (Exception e) + catch (Exception) { await DisconnectAudioAsync().ConfigureAwait(false); - throw e; + throw; } } From 683b3f2701087db6d49c3a54c6d99c80ef5b2e5a Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 28 May 2018 17:12:28 +0100 Subject: [PATCH 044/106] Mark ChannelHelper.IsNsfw Obsolete Discord no longer treats channels prefixed with 'nsfw' as NSFW, so we no longer need to have this check. --- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 2 ++ src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 8 ++++---- .../Entities/Channels/SocketTextChannel.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 4047b7014..fa169a977 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -338,8 +338,10 @@ namespace Discord.Rest return author; } + [Obsolete("Use channel.IsNsfw instead")] public static bool IsNsfw(IChannel channel) => IsNsfw(channel.Name); + [Obsolete("Use Channel.IsNsfw instead")] public static bool IsNsfw(string channelName) => channelName == "nsfw" || channelName.StartsWith("nsfw-"); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index a08585cd8..7f57c96ff 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -17,7 +17,7 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); private bool _nsfw; - public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); + public bool IsNsfw => _nsfw; internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) @@ -47,7 +47,7 @@ namespace Discord.Rest => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); - + public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) @@ -89,7 +89,7 @@ namespace Discord.Rest => ChannelHelper.GetWebhookAsync(this, Discord, id, options); public Task> GetWebhooksAsync(RequestOptions options = null) => ChannelHelper.GetWebhooksAsync(this, Discord, options); - + public Task GetCategoryAsync(RequestOptions options = null) => ChannelHelper.GetCategoryAsync(this, Discord, options); @@ -118,7 +118,7 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } - + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 1d8041585..e41c2ba94 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -21,7 +21,7 @@ namespace Discord.WebSocket => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; private bool _nsfw; - public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); + public bool IsNsfw => _nsfw; public string Mention => MentionUtils.MentionChannel(Id); public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); From b0042606b628e02ffe2bee3135c7ed42c3b8311f Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 28 May 2018 17:13:59 +0100 Subject: [PATCH 045/106] Move RpcCategoryChannel into the correct project --- .../Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src => experiment}/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs (100%) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs b/experiment/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs similarity index 100% rename from src/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs rename to experiment/Discord.Net.Rpc/Entities/Channels/RpcCategoryChannel.cs From 415e2f773978353025ea8b2f6c3d9929601c28c1 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 28 May 2018 17:14:18 +0100 Subject: [PATCH 046/106] Fix analyzer warnings with tests Use (actual, expected) format for Assert.Equal, fixes analyzer warnings --- .../Tests.ChannelPermissions.cs | 44 +++++++-------- test/Discord.Net.Tests/Tests.Channels.cs | 4 +- .../Tests.GuildPermissions.cs | 54 +++++++++---------- test/Discord.Net.Tests/Tests.Permissions.cs | 23 ++++---- 4 files changed, 63 insertions(+), 62 deletions(-) diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index 51834e30d..00306733d 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -82,7 +82,7 @@ namespace Discord ulong dmChannel = (ulong)( ChannelPermission.ViewChannel | ChannelPermission.SendMessages - | ChannelPermission.EmbedLinks + | ChannelPermission.EmbedLinks | ChannelPermission.AttachFiles | ChannelPermission.ReadMessageHistory | ChannelPermission.UseExternalEmojis @@ -118,7 +118,7 @@ namespace Discord // ensure that when modified it works perm = perm.Modify(createInstantInvite: true); Assert.True(perm.CreateInstantInvite); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.CreateInstantInvite); + Assert.Equal((ulong)ChannelPermission.CreateInstantInvite, perm.RawValue); // set false again, move on to next permission perm = perm.Modify(createInstantInvite: false); @@ -130,7 +130,7 @@ namespace Discord perm = perm.Modify(manageChannel: true); Assert.True(perm.ManageChannel); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ManageChannels); + Assert.Equal((ulong)ChannelPermission.ManageChannels, perm.RawValue); perm = perm.Modify(manageChannel: false); Assert.False(perm.ManageChannel); @@ -141,7 +141,7 @@ namespace Discord perm = perm.Modify(addReactions: true); Assert.True(perm.AddReactions); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.AddReactions); + Assert.Equal((ulong)ChannelPermission.AddReactions, perm.RawValue); perm = perm.Modify(addReactions: false); Assert.False(perm.AddReactions); @@ -152,7 +152,7 @@ namespace Discord perm = perm.Modify(viewChannel: true); Assert.True(perm.ViewChannel); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ViewChannel); + Assert.Equal((ulong)ChannelPermission.ViewChannel, perm.RawValue); perm = perm.Modify(viewChannel: false); Assert.False(perm.ViewChannel); @@ -163,7 +163,7 @@ namespace Discord perm = perm.Modify(sendMessages: true); Assert.True(perm.SendMessages); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.SendMessages); + Assert.Equal((ulong)ChannelPermission.SendMessages, perm.RawValue); perm = perm.Modify(sendMessages: false); Assert.False(perm.SendMessages); @@ -174,7 +174,7 @@ namespace Discord perm = perm.Modify(sendTTSMessages: true); Assert.True(perm.SendTTSMessages); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.SendTTSMessages); + Assert.Equal((ulong)ChannelPermission.SendTTSMessages, perm.RawValue); perm = perm.Modify(sendTTSMessages: false); Assert.False(perm.SendTTSMessages); @@ -185,7 +185,7 @@ namespace Discord perm = perm.Modify(manageMessages: true); Assert.True(perm.ManageMessages); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ManageMessages); + Assert.Equal((ulong)ChannelPermission.ManageMessages, perm.RawValue); perm = perm.Modify(manageMessages: false); Assert.False(perm.ManageMessages); @@ -196,7 +196,7 @@ namespace Discord perm = perm.Modify(embedLinks: true); Assert.True(perm.EmbedLinks); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.EmbedLinks); + Assert.Equal((ulong)ChannelPermission.EmbedLinks, perm.RawValue); perm = perm.Modify(embedLinks: false); Assert.False(perm.EmbedLinks); @@ -207,7 +207,7 @@ namespace Discord perm = perm.Modify(attachFiles: true); Assert.True(perm.AttachFiles); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.AttachFiles); + Assert.Equal((ulong)ChannelPermission.AttachFiles, perm.RawValue); perm = perm.Modify(attachFiles: false); Assert.False(perm.AttachFiles); @@ -218,7 +218,7 @@ namespace Discord perm = perm.Modify(readMessageHistory: true); Assert.True(perm.ReadMessageHistory); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ReadMessageHistory); + Assert.Equal((ulong)ChannelPermission.ReadMessageHistory, perm.RawValue); perm = perm.Modify(readMessageHistory: false); Assert.False(perm.ReadMessageHistory); @@ -229,7 +229,7 @@ namespace Discord perm = perm.Modify(mentionEveryone: true); Assert.True(perm.MentionEveryone); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.MentionEveryone); + Assert.Equal((ulong)ChannelPermission.MentionEveryone, perm.RawValue); perm = perm.Modify(mentionEveryone: false); Assert.False(perm.MentionEveryone); @@ -240,7 +240,7 @@ namespace Discord perm = perm.Modify(useExternalEmojis: true); Assert.True(perm.UseExternalEmojis); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.UseExternalEmojis); + Assert.Equal((ulong)ChannelPermission.UseExternalEmojis, perm.RawValue); perm = perm.Modify(useExternalEmojis: false); Assert.False(perm.UseExternalEmojis); @@ -251,18 +251,18 @@ namespace Discord perm = perm.Modify(connect: true); Assert.True(perm.Connect); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.Connect); + Assert.Equal((ulong)ChannelPermission.Connect, perm.RawValue); perm = perm.Modify(connect: false); Assert.False(perm.Connect); Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); - + // individual permission test Assert.False(perm.Speak); perm = perm.Modify(speak: true); Assert.True(perm.Speak); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.Speak); + Assert.Equal((ulong)ChannelPermission.Speak, perm.RawValue); perm = perm.Modify(speak: false); Assert.False(perm.Speak); @@ -273,7 +273,7 @@ namespace Discord perm = perm.Modify(muteMembers: true); Assert.True(perm.MuteMembers); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.MuteMembers); + Assert.Equal((ulong)ChannelPermission.MuteMembers, perm.RawValue); perm = perm.Modify(muteMembers: false); Assert.False(perm.MuteMembers); @@ -284,7 +284,7 @@ namespace Discord perm = perm.Modify(deafenMembers: true); Assert.True(perm.DeafenMembers); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.DeafenMembers); + Assert.Equal((ulong)ChannelPermission.DeafenMembers, perm.RawValue); perm = perm.Modify(deafenMembers: false); Assert.False(perm.DeafenMembers); @@ -295,7 +295,7 @@ namespace Discord perm = perm.Modify(moveMembers: true); Assert.True(perm.MoveMembers); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.MoveMembers); + Assert.Equal((ulong)ChannelPermission.MoveMembers, perm.RawValue); perm = perm.Modify(moveMembers: false); Assert.False(perm.MoveMembers); @@ -306,7 +306,7 @@ namespace Discord perm = perm.Modify(useVoiceActivation: true); Assert.True(perm.UseVAD); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.UseVAD); + Assert.Equal((ulong)ChannelPermission.UseVAD, perm.RawValue); perm = perm.Modify(useVoiceActivation: false); Assert.False(perm.UseVAD); @@ -317,7 +317,7 @@ namespace Discord perm = perm.Modify(manageRoles: true); Assert.True(perm.ManageRoles); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ManageRoles); + Assert.Equal((ulong)ChannelPermission.ManageRoles, perm.RawValue); perm = perm.Modify(manageRoles: false); Assert.False(perm.ManageRoles); @@ -328,7 +328,7 @@ namespace Discord perm = perm.Modify(manageWebhooks: true); Assert.True(perm.ManageWebhooks); - Assert.Equal(perm.RawValue, (ulong)ChannelPermission.ManageWebhooks); + Assert.Equal((ulong)ChannelPermission.ManageWebhooks, perm.RawValue); perm = perm.Modify(manageWebhooks: false); Assert.False(perm.ManageWebhooks); diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index 2c189c03f..46e28b9da 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -169,7 +169,7 @@ namespace Discord private async Task CheckChannelCategories(RestCategoryChannel[] categories, RestGuildChannel[] allChannels) { // 2 categories - Assert.Equal(categories.Length, 2); + Assert.Equal(2, categories.Length); var cat1 = categories.Where(x => x.Name == "cat1").FirstOrDefault(); var cat2 = categories.Where(x => x.Name == "cat2").FirstOrDefault(); @@ -202,7 +202,7 @@ namespace Discord Assert.NotNull(voice1); Assert.NotNull(voice3); - + Assert.Equal(voice1.CategoryId, cat2.Id); var voice1Cat = await voice1.GetCategoryAsync(); Assert.Equal(voice1Cat.Id, cat2.Id); diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index defaf4d02..0f813377e 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -49,7 +49,7 @@ namespace Discord Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue); Assert.Equal((ulong)0, GuildPermissions.None.RawValue); - // assert that GuildPermissions.All contains the same number of permissions as the + // assert that GuildPermissions.All contains the same number of permissions as the // GuildPermissions enum Assert.Equal(Enum.GetValues(typeof(GuildPermission)).Length, GuildPermissions.All.ToList().Count); @@ -76,7 +76,7 @@ namespace Discord // ensure that when we modify it the parameter works perm = perm.Modify(createInstantInvite: true); Assert.True(perm.CreateInstantInvite); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite); + Assert.Equal((ulong)GuildPermission.CreateInstantInvite, perm.RawValue); // set it false again, then move on to the next permission perm = perm.Modify(createInstantInvite: false); @@ -86,7 +86,7 @@ namespace Discord // individual permission test perm = perm.Modify(kickMembers: true); Assert.True(perm.KickMembers); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.KickMembers); + Assert.Equal((ulong)GuildPermission.KickMembers, perm.RawValue); perm = perm.Modify(kickMembers: false); Assert.False(perm.KickMembers); @@ -95,7 +95,7 @@ namespace Discord // individual permission test perm = perm.Modify(banMembers: true); Assert.True(perm.BanMembers); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.BanMembers); + Assert.Equal((ulong)GuildPermission.BanMembers, perm.RawValue); perm = perm.Modify(banMembers: false); Assert.False(perm.BanMembers); @@ -104,7 +104,7 @@ namespace Discord // individual permission test perm = perm.Modify(administrator: true); Assert.True(perm.Administrator); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.Administrator); + Assert.Equal((ulong)GuildPermission.Administrator, perm.RawValue); perm = perm.Modify(administrator: false); Assert.False(perm.Administrator); @@ -113,7 +113,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageChannels: true); Assert.True(perm.ManageChannels); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels); + Assert.Equal((ulong)GuildPermission.ManageChannels, perm.RawValue); perm = perm.Modify(manageChannels: false); Assert.False(perm.ManageChannels); @@ -122,7 +122,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageGuild: true); Assert.True(perm.ManageGuild); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageGuild); + Assert.Equal((ulong)GuildPermission.ManageGuild, perm.RawValue); perm = perm.Modify(manageGuild: false); Assert.False(perm.ManageGuild); @@ -132,7 +132,7 @@ namespace Discord // individual permission test perm = perm.Modify(addReactions: true); Assert.True(perm.AddReactions); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions); + Assert.Equal((ulong)GuildPermission.AddReactions, perm.RawValue); perm = perm.Modify(addReactions: false); Assert.False(perm.AddReactions); @@ -142,7 +142,7 @@ namespace Discord // individual permission test perm = perm.Modify(viewAuditLog: true); Assert.True(perm.ViewAuditLog); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewAuditLog); + Assert.Equal((ulong)GuildPermission.ViewAuditLog, perm.RawValue); perm = perm.Modify(viewAuditLog: false); Assert.False(perm.ViewAuditLog); @@ -152,7 +152,7 @@ namespace Discord // individual permission test perm = perm.Modify(viewChannel: true); Assert.True(perm.ViewChannel); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewChannel); + Assert.Equal((ulong)GuildPermission.ViewChannel, perm.RawValue); perm = perm.Modify(viewChannel: false); Assert.False(perm.ViewChannel); @@ -162,7 +162,7 @@ namespace Discord // individual permission test perm = perm.Modify(sendMessages: true); Assert.True(perm.SendMessages); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages); + Assert.Equal((ulong)GuildPermission.SendMessages, perm.RawValue); perm = perm.Modify(sendMessages: false); Assert.False(perm.SendMessages); @@ -171,7 +171,7 @@ namespace Discord // individual permission test perm = perm.Modify(embedLinks: true); Assert.True(perm.EmbedLinks); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks); + Assert.Equal((ulong)GuildPermission.EmbedLinks, perm.RawValue); perm = perm.Modify(embedLinks: false); Assert.False(perm.EmbedLinks); @@ -180,7 +180,7 @@ namespace Discord // individual permission test perm = perm.Modify(attachFiles: true); Assert.True(perm.AttachFiles); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles); + Assert.Equal((ulong)GuildPermission.AttachFiles, perm.RawValue); perm = perm.Modify(attachFiles: false); Assert.False(perm.AttachFiles); @@ -189,7 +189,7 @@ namespace Discord // individual permission test perm = perm.Modify(readMessageHistory: true); Assert.True(perm.ReadMessageHistory); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory); + Assert.Equal((ulong)GuildPermission.ReadMessageHistory, perm.RawValue); perm = perm.Modify(readMessageHistory: false); Assert.False(perm.ReadMessageHistory); @@ -198,7 +198,7 @@ namespace Discord // individual permission test perm = perm.Modify(mentionEveryone: true); Assert.True(perm.MentionEveryone); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone); + Assert.Equal((ulong)GuildPermission.MentionEveryone, perm.RawValue); perm = perm.Modify(mentionEveryone: false); Assert.False(perm.MentionEveryone); @@ -207,7 +207,7 @@ namespace Discord // individual permission test perm = perm.Modify(useExternalEmojis: true); Assert.True(perm.UseExternalEmojis); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis); + Assert.Equal((ulong)GuildPermission.UseExternalEmojis, perm.RawValue); perm = perm.Modify(useExternalEmojis: false); Assert.False(perm.UseExternalEmojis); @@ -216,7 +216,7 @@ namespace Discord // individual permission test perm = perm.Modify(connect: true); Assert.True(perm.Connect); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect); + Assert.Equal((ulong)GuildPermission.Connect, perm.RawValue); perm = perm.Modify(connect: false); Assert.False(perm.Connect); @@ -225,7 +225,7 @@ namespace Discord // individual permission test perm = perm.Modify(speak: true); Assert.True(perm.Speak); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak); + Assert.Equal((ulong)GuildPermission.Speak, perm.RawValue); perm = perm.Modify(speak: false); Assert.False(perm.Speak); @@ -234,7 +234,7 @@ namespace Discord // individual permission test perm = perm.Modify(muteMembers: true); Assert.True(perm.MuteMembers); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers); + Assert.Equal((ulong)GuildPermission.MuteMembers, perm.RawValue); perm = perm.Modify(muteMembers: false); Assert.False(perm.MuteMembers); @@ -243,7 +243,7 @@ namespace Discord // individual permission test perm = perm.Modify(deafenMembers: true); Assert.True(perm.DeafenMembers); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers); + Assert.Equal((ulong)GuildPermission.DeafenMembers, perm.RawValue); perm = perm.Modify(deafenMembers: false); Assert.False(perm.DeafenMembers); @@ -252,7 +252,7 @@ namespace Discord // individual permission test perm = perm.Modify(moveMembers: true); Assert.True(perm.MoveMembers); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers); + Assert.Equal((ulong)GuildPermission.MoveMembers, perm.RawValue); perm = perm.Modify(moveMembers: false); Assert.False(perm.MoveMembers); @@ -261,7 +261,7 @@ namespace Discord // individual permission test perm = perm.Modify(useVoiceActivation: true); Assert.True(perm.UseVAD); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD); + Assert.Equal((ulong)GuildPermission.UseVAD, perm.RawValue); perm = perm.Modify(useVoiceActivation: false); Assert.False(perm.UseVAD); @@ -270,7 +270,7 @@ namespace Discord // individual permission test perm = perm.Modify(changeNickname: true); Assert.True(perm.ChangeNickname); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ChangeNickname); + Assert.Equal((ulong)GuildPermission.ChangeNickname, perm.RawValue); perm = perm.Modify(changeNickname: false); Assert.False(perm.ChangeNickname); @@ -279,7 +279,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageNicknames: true); Assert.True(perm.ManageNicknames); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageNicknames); + Assert.Equal((ulong)GuildPermission.ManageNicknames, perm.RawValue); perm = perm.Modify(manageNicknames: false); Assert.False(perm.ManageNicknames); @@ -288,7 +288,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageRoles: true); Assert.True(perm.ManageRoles); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles); + Assert.Equal((ulong)GuildPermission.ManageRoles, perm.RawValue); perm = perm.Modify(manageRoles: false); Assert.False(perm.ManageRoles); @@ -297,7 +297,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageWebhooks: true); Assert.True(perm.ManageWebhooks); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks); + Assert.Equal((ulong)GuildPermission.ManageWebhooks, perm.RawValue); perm = perm.Modify(manageWebhooks: false); Assert.False(perm.ManageWebhooks); @@ -306,7 +306,7 @@ namespace Discord // individual permission test perm = perm.Modify(manageEmojis: true); Assert.True(perm.ManageEmojis); - Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageEmojis); + Assert.Equal((ulong)GuildPermission.ManageEmojis, perm.RawValue); perm = perm.Modify(manageEmojis: false); Assert.False(perm.ManageEmojis); diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 5a7b5a16b..f87522105 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -77,7 +77,7 @@ namespace Discord /// /// Tests for the class. - /// + /// /// Tests that text channel permissions get the right value /// from the Has method. /// @@ -114,7 +114,7 @@ namespace Discord /// /// Tests for the class. - /// + /// /// Tests that no channel permissions get the right value /// from the Has method. /// @@ -151,7 +151,7 @@ namespace Discord /// /// Tests for the class. - /// + /// /// Tests that the dm channel permissions get the right value /// from the Has method. /// @@ -188,7 +188,7 @@ namespace Discord /// /// Tests for the class. - /// + /// /// Tests that the group channel permissions get the right value /// from the Has method. /// @@ -225,7 +225,7 @@ namespace Discord /// /// Tests for the class. - /// + /// /// Tests that the voice channel permissions get the right value /// from the Has method. /// @@ -262,8 +262,8 @@ namespace Discord /// /// Tests for the class. - /// - /// Test that that the Has method of + /// + /// Test that that the Has method of /// returns the correct value when no permissions are set. /// /// @@ -305,8 +305,8 @@ namespace Discord /// /// Tests for the class. - /// - /// Test that that the Has method of + /// + /// Test that that the Has method of /// returns the correct value when all permissions are set. /// /// @@ -349,8 +349,8 @@ namespace Discord /// /// Tests for the class. - /// - /// Test that that the Has method of + /// + /// Test that that the Has method of /// returns the correct value when webhook permissions are set. /// /// @@ -709,6 +709,7 @@ namespace Discord /// of the OverwritePermissions. /// /// + [Fact] public Task TestOverwritePermissionModifyNoParam() { // test for all Text allowed, none denied From 0c7cb73b06aba3eaeb811b4671fd0f4a5078a491 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 28 May 2018 17:41:05 +0100 Subject: [PATCH 047/106] Fix permission-related ViewChannel tests --- test/Discord.Net.Tests/Tests.ChannelPermissions.cs | 1 + test/Discord.Net.Tests/Tests.GuildPermissions.cs | 9 +++++++-- test/Discord.Net.Tests/Tests.Permissions.cs | 8 +++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index 00306733d..dd87c2e24 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -68,6 +68,7 @@ namespace Discord ulong voiceChannel = (ulong)( ChannelPermission.CreateInstantInvite | ChannelPermission.ManageChannels + | ChannelPermission.ViewChannel | ChannelPermission.Connect | ChannelPermission.Speak | ChannelPermission.MuteMembers diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index 0f813377e..bbd6621b5 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -38,9 +39,13 @@ namespace Discord copy = GuildPermissions.Webhook.Modify(); Assert.Equal(GuildPermissions.Webhook.RawValue, copy.RawValue); + // Get all distinct values (ReadMessages = ViewChannel) + var enumValues = (Enum.GetValues(typeof(GuildPermission)) as GuildPermission[]) + .Distinct() + .ToArray(); // test GuildPermissions.All ulong sumOfAllGuildPermissions = 0; - foreach(var v in Enum.GetValues(typeof(GuildPermission))) + foreach(var v in enumValues) { sumOfAllGuildPermissions |= (ulong)v; } @@ -51,7 +56,7 @@ namespace Discord // assert that GuildPermissions.All contains the same number of permissions as the // GuildPermissions enum - Assert.Equal(Enum.GetValues(typeof(GuildPermission)).Length, GuildPermissions.All.ToList().Count); + Assert.Equal(enumValues.Length, GuildPermissions.All.ToList().Count); // assert that webhook has the same raw value ulong webHookPermissions = (ulong)( diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index f87522105..258e2995d 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using Xunit; @@ -21,6 +22,7 @@ namespace Discord /// private void TestHelper(ulong rawValue, ulong flagValue, bool expected) { + Debug.Print($"Expecting {expected} for {rawValue} w/ {flagValue}"); Assert.Equal(expected, Permissions.GetValue(rawValue, flagValue)); // check that toggling the bit works @@ -239,7 +241,7 @@ namespace Discord TestHelper(value, ChannelPermission.CreateInstantInvite, true); TestHelper(value, ChannelPermission.ManageChannels, true); TestHelper(value, ChannelPermission.AddReactions, false); - TestHelper(value, ChannelPermission.ViewChannel, false); + TestHelper(value, ChannelPermission.ViewChannel, true); TestHelper(value, ChannelPermission.SendMessages, false); TestHelper(value, ChannelPermission.SendTTSMessages, false); TestHelper(value, ChannelPermission.ManageMessages, false); @@ -659,7 +661,7 @@ namespace Discord TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Allow); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Allow); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); @@ -682,7 +684,7 @@ namespace Discord TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Deny); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Deny); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); From a718a7d3c234a37d1c792ac84af0ae8dce61c1a5 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 28 May 2018 12:45:49 -0400 Subject: [PATCH 048/106] cleanup: Remove ChannelHelper#IsNsfw Closes #1074 This code was not referenced anywhere in the active codebase. --- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index fa169a977..50cb352a7 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -337,12 +337,5 @@ namespace Discord.Rest author = RestUser.Create(client, guild, model, webhookId); return author; } - - [Obsolete("Use channel.IsNsfw instead")] - public static bool IsNsfw(IChannel channel) - => IsNsfw(channel.Name); - [Obsolete("Use Channel.IsNsfw instead")] - public static bool IsNsfw(string channelName) => - channelName == "nsfw" || channelName.StartsWith("nsfw-"); } } From aeb30955932803e88fcd056f817f449e8664715f Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 28 May 2018 17:48:38 +0100 Subject: [PATCH 049/106] Remove Debug.Print call causing tests to fail Forgot to hit save properly... Whoops. --- test/Discord.Net.Tests/Tests.Permissions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 258e2995d..2f72f272d 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Threading.Tasks; using Xunit; @@ -22,7 +21,6 @@ namespace Discord /// private void TestHelper(ulong rawValue, ulong flagValue, bool expected) { - Debug.Print($"Expecting {expected} for {rawValue} w/ {flagValue}"); Assert.Equal(expected, Permissions.GetValue(rawValue, flagValue)); // check that toggling the bit works From 64d8938ed5100d4c8f3a2afaec3a6becb53cf9b8 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 28 May 2018 15:25:46 -0400 Subject: [PATCH 050/106] fix: Discord does not always send the member on VOICE_STATE_UPDATE some guilds aren't on g250k yet? not sure --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 2dccd9321..793d2b29c 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1429,7 +1429,9 @@ namespace Discord.WebSocket after = SocketVoiceState.Create(null, data); } - user = guild.GetUser(data.UserId) ?? guild.AddOrUpdateUser(data.Member.Value); //per g250k, this is always sent + // per g250k, this should always be sent, but apparently not always + user = guild.GetUser(data.UserId) + ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); if (user == null) { await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); From 9ba38d77966897037cb6355eda2215fd9a235712 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 28 May 2018 15:35:59 -0400 Subject: [PATCH 051/106] api: upgrade audio client to VoiceWS v3 --- .../API/Voice/HelloEvent.cs | 10 ++++++++++ .../API/Voice/ReadyEvent.cs | 4 +++- .../API/Voice/VoiceOpCode.cs | 16 ++++++++++++---- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 14 ++++++++++---- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs diff --git a/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs new file mode 100644 index 000000000..8fdb0808f --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Voice +{ + internal class HelloEvent + { + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs index 2a134ced1..7188cd8f7 100644 --- a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs @@ -1,5 +1,6 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; +using System; namespace Discord.API.Voice { @@ -14,6 +15,7 @@ namespace Discord.API.Voice [JsonProperty("modes")] public string[] Modes { get; set; } [JsonProperty("heartbeat_interval")] + [Obsolete("This field is errorneous and should not be used", true)] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs index ae11a4c8f..67afe6173 100644 --- a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs +++ b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 namespace Discord.API.Voice { internal enum VoiceOpCode : byte @@ -11,11 +11,19 @@ namespace Discord.API.Voice Ready = 2, /// C→S - Used to keep the connection alive and measure latency. Heartbeat = 3, - /// C←S - Used to reply to a client's heartbeat. - HeartbeatAck = 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 + Speaking = 5, + /// C←S - Used to reply to a client's heartbeat. + HeartbeatAck = 6, + /// C→S - Used to resume a connection. + Resume = 7, + /// C←S - Used to inform the client the heartbeat interval. + Hello = 8, + /// C←S - Used to acknowledge a resumed connection. + Resumed = 9, + /// C←S - Used to notify that a client has disconnected. + ClientDisconnect = 13, } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 1f33b3cc5..4e10d0c54 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -1,4 +1,4 @@ -using Discord.API.Voice; +using Discord.API.Voice; using Discord.Audio.Streams; using Discord.Logging; using Discord.Net.Converters; @@ -107,7 +107,7 @@ namespace Discord.Audio private async Task OnConnectingAsync() { await _audioLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); - await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); + await ApiClient.ConnectAsync("wss://" + _url + "?v=3").ConfigureAwait(false); await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); @@ -216,6 +216,14 @@ namespace Discord.Audio { switch (opCode) { + case VoiceOpCode.Hello: + { + await _audioLogger.DebugAsync("Received Hello").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + + _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); + } + break; case VoiceOpCode.Ready: { await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); @@ -225,8 +233,6 @@ namespace Discord.Audio if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode)) throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}"); - - _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); ApiClient.SetUdpEndpoint(data.Ip, data.Port); await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false); From 513a489bf82ed4e845ff3df9fe21c06e80bb28b8 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 28 May 2018 15:45:42 -0400 Subject: [PATCH 052/106] codefix: add VoiceAPIVersion constant to DiscordConfig --- src/Discord.Net.Core/DiscordConfig.cs | 1 + src/Discord.Net.WebSocket/Audio/AudioClient.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 5cdf6a77e..8c6cfc28c 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -5,6 +5,7 @@ namespace Discord public class DiscordConfig { public const int APIVersion = 6; + public const int VoiceAPIVersion = 3; public static string Version { get; } = typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 4e10d0c54..fb97c1dc9 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -107,7 +107,7 @@ namespace Discord.Audio private async Task OnConnectingAsync() { await _audioLogger.DebugAsync("Connecting ApiClient").ConfigureAwait(false); - await ApiClient.ConnectAsync("wss://" + _url + "?v=3").ConfigureAwait(false); + await ApiClient.ConnectAsync("wss://" + _url + "?v=" + DiscordConfig.VoiceAPIVersion).ConfigureAwait(false); await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); From 3acf2a9a6bb13d5b75c09babaf7234cb2707f4e8 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Mon, 28 May 2018 14:59:30 -0500 Subject: [PATCH 053/106] Refactors readMessages, fixing from #1033 (#1075) --- .../Entities/Permissions/OverwritePermissions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index 54a4e4035..b8b4b83e2 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -126,7 +126,7 @@ namespace Discord PermValue createInstantInvite = PermValue.Inherit, PermValue manageChannel = PermValue.Inherit, PermValue addReactions = PermValue.Inherit, - PermValue readMessages = PermValue.Inherit, + PermValue viewChannel = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, @@ -143,7 +143,7 @@ namespace Discord PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) - : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, + : this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } @@ -152,7 +152,7 @@ namespace Discord PermValue? createInstantInvite = null, PermValue? manageChannel = null, PermValue? addReactions = null, - PermValue? readMessages = null, + PermValue? viewChannel = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, @@ -169,7 +169,7 @@ namespace Discord PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null) - => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, + => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); From 890904f32cf6c38abea60149b858e2bf8f63154a Mon Sep 17 00:00:00 2001 From: NovusTheory Date: Thu, 31 May 2018 16:21:57 -0500 Subject: [PATCH 054/106] Allow external VoiceChannel client API implementation (#1057) * ConnectAsync(bool external) API implemented This allows developers to handle the AudioClient externally (for example with Lavalink) * Modify ConnectAsync API and add DisconnectAsync to IAudioChannel * Update summary message on IAudioChannel.DisconnectAsync() * Review changes: - Fix `if (!external)` styling - Remove unecessary ConnectAsync overload * SocketVoiceChannel overload update * Update IAudioChannel.ConnectAsync() - Remove ConfigAction from parameters - Add SelfDeafen/SelfMute to parameters * Remove SocketVoiceChannel.ConnectAsync() default params (Inherit) --- .../Entities/Channels/IAudioChannel.cs | 7 ++++-- .../Entities/Channels/RestGroupChannel.cs | 3 ++- .../Entities/Channels/RestVoiceChannel.cs | 3 ++- .../Entities/Channels/SocketGroupChannel.cs | 3 ++- .../Entities/Channels/SocketVoiceChannel.cs | 7 ++++-- .../Entities/Guilds/SocketGuild.cs | 24 ++++++++++++------- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs index afb81d92f..a152ff744 100644 --- a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -1,4 +1,4 @@ -using Discord.Audio; +using Discord.Audio; using System; using System.Threading.Tasks; @@ -7,6 +7,9 @@ namespace Discord public interface IAudioChannel : IChannel { /// Connects to this audio channel. - Task ConnectAsync(Action configAction = null); + Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false); + + /// Disconnects from this audio channel. + Task DisconnectAsync(); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 367abfb4a..5ac0f2c40 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -147,7 +147,8 @@ namespace Discord.Rest => EnterTypingState(options); //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } + Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } //IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index a2bead45f..13f3b5efa 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -45,7 +45,8 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } + Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } //IGuildChannel Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 81b5cfbf3..125625456 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -211,7 +211,8 @@ namespace Discord.WebSocket => EnterTypingState(options); //IAudioChannel - Task IAudioChannel.ConnectAsync(Action configAction) { throw new NotSupportedException(); } + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } + Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } //IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 349621fac..f967e6e6a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -43,11 +43,14 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); - public async Task ConnectAsync(Action configAction = null) + public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) { - return await Guild.ConnectAudioAsync(Id, false, false, configAction).ConfigureAwait(false); + return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); } + public async Task DisconnectAsync() + => await Guild.DisconnectAudioAsync(); + public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 14263f0af..f41f61a7c 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -508,11 +508,8 @@ namespace Discord.WebSocket { return _audioClient?.GetInputStream(userId); } - internal async Task ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute, Action configAction) + internal async Task ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute, bool external) { - selfDeaf = false; - selfMute = false; - TaskCompletionSource promise; await _audioLock.WaitAsync().ConfigureAwait(false); @@ -522,6 +519,13 @@ namespace Discord.WebSocket promise = new TaskCompletionSource(); _audioConnectPromise = promise; + if (external) + { + var _ = promise.TrySetResultAsync(null); + await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); + return null; + } + if (_audioClient == null) { var audioClient = new AudioClient(this, Discord.GetAudioId(), channelId); @@ -529,7 +533,9 @@ namespace Discord.WebSocket { if (!promise.Task.IsCompleted) { - try { audioClient.Dispose(); } catch { } + try + { audioClient.Dispose(); } + catch { } _audioClient = null; if (ex != null) await promise.TrySetExceptionAsync(ex); @@ -543,7 +549,6 @@ namespace Discord.WebSocket var _ = promise.TrySetResultAsync(_audioClient); return Task.Delay(0); }; - configAction?.Invoke(audioClient); _audioClient = audioClient; } @@ -602,8 +607,11 @@ namespace Discord.WebSocket await _audioLock.WaitAsync().ConfigureAwait(false); try { - await RepopulateAudioStreamsAsync().ConfigureAwait(false); - await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); + if (_audioClient != null) + { + await RepopulateAudioStreamsAsync().ConfigureAwait(false); + await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); + } } catch (OperationCanceledException) { From 48fed064d446ca4a1c685893320e09b638eace89 Mon Sep 17 00:00:00 2001 From: NovusTheory Date: Sat, 16 Jun 2018 22:34:12 -0500 Subject: [PATCH 055/106] Add missing VoiceServerUpdated event (#1089) --- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index dad53b3b7..26d9363eb 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -309,6 +309,7 @@ namespace Discord.WebSocket client.UserUpdated += (oldUser, newUser) => _userUpdatedEvent.InvokeAsync(oldUser, newUser); client.GuildMemberUpdated += (oldUser, newUser) => _guildMemberUpdatedEvent.InvokeAsync(oldUser, newUser); client.UserVoiceStateUpdated += (user, oldVoiceState, newVoiceState) => _userVoiceStateUpdatedEvent.InvokeAsync(user, oldVoiceState, newVoiceState); + client.VoiceServerUpdated += (server) => _voiceServerUpdatedEvent.InvokeAsync(server); client.CurrentUserUpdated += (oldUser, newUser) => _selfUpdatedEvent.InvokeAsync(oldUser, newUser); client.UserIsTyping += (oldUser, newUser) => _userIsTypingEvent.InvokeAsync(oldUser, newUser); client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user); From 93878e4a9061253dd8f5874af75057cc71924f0d Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Mon, 18 Jun 2018 14:57:58 -0500 Subject: [PATCH 056/106] Refactors an unused parameter in CommandService#Search (#1025) --- src/Discord.Net.Commands/CommandService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index f75653ccf..8e5297e49 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -322,8 +322,10 @@ namespace Discord.Commands //Execution public SearchResult Search(ICommandContext context, int argPos) - => Search(context, context.Message.Content.Substring(argPos)); + => Search(context.Message.Content.Substring(argPos)); public SearchResult Search(ICommandContext context, string input) + => Search(input); + public SearchResult Search(string input) { string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); @@ -339,7 +341,8 @@ namespace Discord.Commands public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; - var searchResult = Search(context, input); + + var searchResult = Search(input); if (!searchResult.IsSuccess) return searchResult; From 033d31294f54945a2b9b2aa17c8d356fcffb4c83 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Mon, 18 Jun 2018 15:00:40 -0500 Subject: [PATCH 057/106] Creates full guild user object, resolves #1047 (#1078) --- src/Discord.Net.Rest/ClientHelper.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index a1f8ece69..0bba0d2c4 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -142,9 +142,14 @@ namespace Discord.Rest public static async Task GetGuildUserAsync(BaseDiscordClient client, ulong guildId, ulong id, RequestOptions options) { + var guild = await GetGuildAsync(client, guildId, options).ConfigureAwait(false); + if (guild == null) + return null; + var model = await client.ApiClient.GetGuildMemberAsync(guildId, id, options).ConfigureAwait(false); if (model != null) - return RestGuildUser.Create(client, new RestGuild(client, guildId), model); + return RestGuildUser.Create(client, guild, model); + return null; } From aff4512fcd446ce4ec6468c2c8b50e66aa86548d Mon Sep 17 00:00:00 2001 From: Builderb Date: Mon, 18 Jun 2018 21:02:08 +0100 Subject: [PATCH 058/106] Update Discord.Net.Core.csproj (#1081) --- src/Discord.Net.Core/Discord.Net.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 6a58367e6..d4d450e1c 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -9,7 +9,7 @@ - + From 322d46e47b47e44f8b62b1ddcdcf39280cac6771 Mon Sep 17 00:00:00 2001 From: ComputerMaster1st Date: Sat, 23 Jun 2018 02:20:53 +0100 Subject: [PATCH 059/106] Solved 4003 Authentication Error (Race Condition) (#1093) * Solved 4003 Authentication Error (Race Condition) * Set static interval & removed hello case --- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index fb97c1dc9..bbb65a298 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -216,14 +216,6 @@ namespace Discord.Audio { switch (opCode) { - case VoiceOpCode.Hello: - { - await _audioLogger.DebugAsync("Received Hello").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - - _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); - } - break; case VoiceOpCode.Ready: { await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); @@ -236,6 +228,9 @@ namespace Discord.Audio ApiClient.SetUdpEndpoint(data.Ip, data.Port); await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false); + + + _heartbeatTask = RunHeartbeatAsync(41250, _connection.CancelToken); } break; case VoiceOpCode.SessionDescription: From 4bc06a0a54477e756caa18d3030e5a8e7fa20d10 Mon Sep 17 00:00:00 2001 From: HelpfulStranger999 Date: Mon, 2 Jul 2018 15:29:42 -0500 Subject: [PATCH 060/106] Patch lazy default readers not getting replaced (#1083) --- src/Discord.Net.Commands/CommandService.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8e5297e49..6fd5d38ad 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -262,7 +262,7 @@ namespace Discord.Commands /// If should replace the default for if one exists. public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault) { - if (replaceDefault && _defaultTypeReaders.ContainsKey(type)) + if (replaceDefault && HasDefaultTypeReader(type)) { _defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader); if (type.GetTypeInfo().IsValueType) @@ -281,6 +281,16 @@ namespace Discord.Commands AddNullableTypeReader(type, reader); } } + internal bool HasDefaultTypeReader(Type type) + { + if (_defaultTypeReaders.ContainsKey(type)) + return true; + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsEnum) + return true; + return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); + } internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary()); From ffe994a9dff1ea3bca63673d332a28b88231d38d Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Tue, 3 Jul 2018 05:02:38 +0800 Subject: [PATCH 061/106] Mark guild optional in invite & general invite improvement (#1094) * Mark guild as optional for invite * Mark partial InviteMetadata members as Optional * Some of them aren't sent when requesting through the general GET invite endpoint * Remove GetInviteParams * It was kinda stupid in the first place, might as well always get the count instead of having to ask the user whether they want the two fields filled or not. * Add ChannelType property * Add vanity invite support --- .../Entities/Channels/ChannelType.cs | 0 src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 9 +++++++++ .../Entities/Invites/IInvite.cs | 6 +++--- .../Entities/Invites/IInviteMetadata.cs | 8 ++++---- src/Discord.Net.Rest/API/Common/Invite.cs | 2 +- .../API/Common/InviteChannel.cs | 4 ++-- .../API/Common/InviteMetadata.cs | 10 +++++----- .../API/Rest/GetInviteParams.cs | 7 ------- src/Discord.Net.Rest/ClientHelper.cs | 8 ++------ src/Discord.Net.Rest/DiscordRestApiClient.cs | 14 ++++++++++---- src/Discord.Net.Rest/DiscordRestClient.cs | 2 +- .../Entities/Guilds/GuildHelper.cs | 6 ++++++ .../Entities/Guilds/RestGuild.cs | 12 ++++++++++++ .../Entities/Invites/RestInvite.cs | 18 ++++++++---------- .../Entities/Invites/RestInviteMetadata.cs | 16 ++++++++-------- src/Discord.Net.WebSocket/BaseSocketClient.cs | 2 +- .../Entities/Guilds/SocketGuild.cs | 12 ++++++++++++ 17 files changed, 84 insertions(+), 52 deletions(-) rename src/{Discord.Net.Rest => Discord.Net.Core}/Entities/Channels/ChannelType.cs (100%) delete mode 100644 src/Discord.Net.Rest/API/Rest/GetInviteParams.cs diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelType.cs b/src/Discord.Net.Core/Entities/Channels/ChannelType.cs similarity index 100% rename from src/Discord.Net.Rest/Entities/Channels/ChannelType.cs rename to src/Discord.Net.Core/Entities/Channels/ChannelType.cs diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 4c1c274d0..604945758 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -120,6 +120,15 @@ namespace Discord /// Gets a collection of all invites to this guild. Task> GetInvitesAsync(RequestOptions options = null); + /// + /// Gets the vanity invite URL of this guild. + /// + /// The options to be used when sending the request. + /// + /// An awaitable containing the partial metadata of the vanity invite found within + /// this guild. + /// + Task GetVanityInviteAsync(RequestOptions options = null); /// Gets the role in this guild with the provided id, or null if not found. IRole GetRole(ulong id); diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 0ebb65679..1ab26de8f 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; - namespace Discord { public interface IInvite : IEntity, IDeletable @@ -11,6 +9,8 @@ namespace Discord /// Gets the channel this invite is linked to. IChannel Channel { get; } + /// Gets the type of the channel this invite is linked to. + ChannelType ChannelType { get; } /// Gets the id of the channel this invite is linked to. ulong ChannelId { get; } /// Gets the name of the channel this invite is linked to. @@ -19,7 +19,7 @@ namespace Discord /// Gets the guild this invite is linked to. IGuild Guild { get; } /// Gets the id of the guild this invite is linked to. - ulong GuildId { get; } + ulong? GuildId { get; } /// Gets the name of the guild this invite is linked to. string GuildName { get; } /// Gets the approximated count of online members in the guild. diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index 1136e1678..0e026ab62 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -15,8 +15,8 @@ namespace Discord /// Gets the max amount of times this invite may be used, or null if there is no limit. int? MaxUses { get; } /// Gets the amount of times this invite has been used. - int Uses { get; } + int? Uses { get; } /// Gets when this invite was created. - DateTimeOffset CreatedAt { get; } + DateTimeOffset? CreatedAt { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index 1b35da870..649bc37ec 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -8,7 +8,7 @@ namespace Discord.API [JsonProperty("code")] public string Code { get; set; } [JsonProperty("guild")] - public InviteGuild Guild { get; set; } + public Optional Guild { get; set; } [JsonProperty("channel")] public InviteChannel Channel { get; set; } [JsonProperty("approximate_presence_count")] diff --git a/src/Discord.Net.Rest/API/Common/InviteChannel.cs b/src/Discord.Net.Rest/API/Common/InviteChannel.cs index ca9699067..f8f2a34f2 100644 --- a/src/Discord.Net.Rest/API/Common/InviteChannel.cs +++ b/src/Discord.Net.Rest/API/Common/InviteChannel.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API @@ -10,6 +10,6 @@ namespace Discord.API [JsonProperty("name")] public string Name { get; set; } [JsonProperty("type")] - public string Type { get; set; } + public int Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index 586307523..ca019b79b 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -9,15 +9,15 @@ namespace Discord.API [JsonProperty("inviter")] public User Inviter { get; set; } [JsonProperty("uses")] - public int Uses { get; set; } + public Optional Uses { get; set; } [JsonProperty("max_uses")] - public int MaxUses { get; set; } + public Optional MaxUses { get; set; } [JsonProperty("max_age")] - public int MaxAge { get; set; } + public Optional MaxAge { get; set; } [JsonProperty("temporary")] public bool Temporary { get; set; } [JsonProperty("created_at")] - public DateTimeOffset CreatedAt { get; set; } + public Optional CreatedAt { get; set; } [JsonProperty("revoked")] public bool Revoked { get; set; } } diff --git a/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs b/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs deleted file mode 100644 index cb8d8f7fe..000000000 --- a/src/Discord.Net.Rest/API/Rest/GetInviteParams.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Discord.API.Rest -{ - internal class GetInviteParams - { - public Optional WithCounts { get; set; } - } -} diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 0bba0d2c4..d8f481d15 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -51,13 +51,9 @@ namespace Discord.Rest } public static async Task GetInviteAsync(BaseDiscordClient client, - string inviteId, bool withCount, RequestOptions options) + string inviteId, RequestOptions options) { - var args = new GetInviteParams - { - WithCounts = withCount - }; - var model = await client.ApiClient.GetInviteAsync(inviteId, args, options).ConfigureAwait(false); + var model = await client.ApiClient.GetInviteAsync(inviteId, options).ConfigureAwait(false); if (model != null) return RestInviteMetadata.Create(client, null, null, model); return null; diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 85e04f962..2236dbbf8 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -906,7 +906,7 @@ namespace Discord.API } //Guild Invites - public async Task GetInviteAsync(string inviteId, GetInviteParams args, RequestOptions options = null) + public async Task GetInviteAsync(string inviteId, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); @@ -919,14 +919,20 @@ namespace Discord.API if (index >= 0) inviteId = inviteId.Substring(index + 1); - var withCounts = args.WithCounts.GetValueOrDefault(false); - try { - return await SendAsync("GET", () => $"invites/{inviteId}?with_counts={withCounts}", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"invites/{inviteId}?with_counts=true", new BucketIds(), options: options).ConfigureAwait(false); } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } + public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(guildId: guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); + } public async Task> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 3a596624d..73028362f 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -57,7 +57,7 @@ namespace Discord.Rest /// public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) - => ClientHelper.GetInviteAsync(this, inviteId, withCount, options); + => ClientHelper.GetInviteAsync(this, inviteId, options); /// public Task GetGuildAsync(ulong id, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index c76b41a91..4bd0e9972 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -210,6 +210,12 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, guild, null, x)).ToImmutableArray(); } + public static async Task GetVanityInviteAsync(IGuild guild, BaseDiscordClient client, + RequestOptions options) + { + var model = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); + return RestInviteMetadata.Create(client, guild, null, model); + } //Roles public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 357e22c85..9b3143268 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -238,6 +238,15 @@ namespace Discord.Rest //Invites public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); + /// + /// Gets the vanity invite URL of this guild. + /// + /// The options to be used when sending the request. + /// + /// A partial metadata of the vanity invite found within this guild. + /// + public Task GetVanityInviteAsync(RequestOptions options = null) + => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles public RestRole GetRole(ulong id) @@ -397,6 +406,9 @@ namespace Discord.Rest async Task> IGuild.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetVanityInviteAsync(RequestOptions options) + => await GetVanityInviteAsync(options).ConfigureAwait(false); IRole IGuild.GetRole(ulong id) => GetRole(id); diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 18698c626..050f117fb 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Discord.API.Rest; using Model = Discord.API.Invite; namespace Discord.Rest @@ -9,14 +8,15 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestInvite : RestEntity, IInvite, IUpdateable { + public ChannelType ChannelType { get; private set; } public string ChannelName { get; private set; } public string GuildName { get; private set; } public int? PresenceCount { get; private set; } public int? MemberCount { get; private set; } public ulong ChannelId { get; private set; } - public ulong GuildId { get; private set; } - internal IChannel Channel { get; private set; } - internal IGuild Guild { get; private set; } + public ulong? GuildId { get; private set; } + internal IChannel Channel { get; } + internal IGuild Guild { get; } public string Code => Id; public string Url => $"{DiscordConfig.InviteUrl}{Code}"; @@ -35,20 +35,18 @@ namespace Discord.Rest } internal void Update(Model model) { - GuildId = model.Guild.Id; + GuildId = model.Guild.IsSpecified ? model.Guild.Value.Id : default(ulong?); ChannelId = model.Channel.Id; - GuildName = model.Guild.Name; + GuildName = model.Guild.IsSpecified ? model.Guild.Value.Name : null; ChannelName = model.Channel.Name; MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; + ChannelType = (ChannelType)model.Channel.Type; } public async Task UpdateAsync(RequestOptions options = null) { - var args = new GetInviteParams(); - if (MemberCount != null || PresenceCount != null) - args.WithCounts = true; - var model = await Discord.ApiClient.GetInviteAsync(Code, args, options).ConfigureAwait(false); + var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); Update(model); } public Task DeleteAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index 42aeb40aa..c7236be58 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -1,20 +1,20 @@ -using System; +using System; using Model = Discord.API.InviteMetadata; namespace Discord.Rest { public class RestInviteMetadata : RestInvite, IInviteMetadata { - private long _createdAtTicks; + private long? _createdAtTicks; 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; } + public int? Uses { get; private set; } public RestUser Inviter { get; private set; } - public DateTimeOffset CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); + public DateTimeOffset? CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); internal RestInviteMetadata(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) : base(discord, guild, channel, id) @@ -32,10 +32,10 @@ namespace Discord.Rest Inviter = model.Inviter != null ? RestUser.Create(Discord, model.Inviter) : null; IsRevoked = model.Revoked; IsTemporary = model.Temporary; - MaxAge = model.MaxAge != 0 ? model.MaxAge : (int?)null; - MaxUses = model.MaxUses; - Uses = model.Uses; - _createdAtTicks = model.CreatedAt.UtcTicks; + MaxAge = model.MaxAge.IsSpecified ? model.MaxAge.Value : (int?)null; + MaxUses = model.MaxUses.IsSpecified ? model.MaxUses.Value : (int?)null; + Uses = model.Uses.IsSpecified ? model.Uses.Value : (int?)null; + _createdAtTicks = model.CreatedAt.IsSpecified ? model.CreatedAt.Value.UtcTicks : (long?)null; } IUser IInviteMetadata.Inviter => Inviter; diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index fb82fe14a..be6f3795d 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -56,7 +56,7 @@ namespace Discord.WebSocket => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); /// public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) - => ClientHelper.GetInviteAsync(this, inviteId, withCount, options ?? RequestOptions.Default); + => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); // IDiscordClient async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index f41f61a7c..680706713 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -345,6 +345,15 @@ namespace Discord.WebSocket //Invites public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); + /// + /// Gets the vanity invite URL of this guild. + /// + /// The options to be used when sending the request. + /// + /// A partial metadata of the vanity invite found within this guild. + /// + public Task GetVanityInviteAsync(RequestOptions options = null) + => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles public SocketRole GetRole(ulong id) @@ -700,6 +709,9 @@ namespace Discord.WebSocket async Task> IGuild.GetInvitesAsync(RequestOptions options) => await GetInvitesAsync(options).ConfigureAwait(false); + /// + async Task IGuild.GetVanityInviteAsync(RequestOptions options) + => await GetVanityInviteAsync(options).ConfigureAwait(false); IRole IGuild.GetRole(ulong id) => GetRole(id); From beb3d46e0866c3d1ee8fa8483576547e90b517c6 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 2 Jul 2018 21:24:50 -0400 Subject: [PATCH 062/106] gateway: Ignore PRESENCES_REPLACE dispatch This dispatch is undocumented and only used by user accounts. --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 793d2b29c..412552d74 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1512,6 +1512,9 @@ namespace Discord.WebSocket case "MESSAGE_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); break; + case "PRESENCES_REPLACE": + await _gatewayLogger.DebugAsync("Ignored Dispatch (PRESENCES_REPLACE)").ConfigureAwait(false); + break; case "USER_SETTINGS_UPDATE": await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); break; From b2f00439524af9836818631f4bfa7c42e9076feb Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Wed, 11 Jul 2018 07:32:29 +0800 Subject: [PATCH 063/106] Fix typo in audit log interface declaration (#1104) --- src/Discord.Net.Core/Entities/Guilds/IGuild.cs | 2 +- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 2 +- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 604945758..bbe7051cb 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -149,7 +149,7 @@ namespace Discord Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); /// Gets the specified number of audit log entries for this guild. - Task> GetAuditLogAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, + Task> GetAuditLogsAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Gets the webhook in this guild with the provided id, or null if not found. diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 9b3143268..9bf13fa07 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -445,7 +445,7 @@ namespace Discord.Rest } Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } - async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) { if (cacheMode == CacheMode.AllowDownload) return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 680706713..bf33ef569 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -727,7 +727,7 @@ namespace Discord.WebSocket Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Owner); - async Task> IGuild.GetAuditLogAsync(int limit, CacheMode cacheMode, RequestOptions options) + async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) { if (cacheMode == CacheMode.AllowDownload) return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); From 245806fe3da063b8d11c26ae3ea7723daab83797 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Thu, 12 Jul 2018 08:44:00 +0800 Subject: [PATCH 064/106] Initial refactor (#1103) --- src/Discord.Net.Core/IDiscordClient.cs | 2 +- src/Discord.Net.Rest/BaseDiscordClient.cs | 2 +- src/Discord.Net.Rest/DiscordRestClient.cs | 6 +++--- src/Discord.Net.WebSocket/BaseSocketClient.cs | 6 +++--- src/Discord.Net.WebSocket/DiscordShardedClient.cs | 4 ++-- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index 083c0b512..a383c37da 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -27,7 +27,7 @@ namespace Discord Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); - Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null); + Task GetInviteAsync(string inviteId, RequestOptions options = null); Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetUserAsync(string username, string discriminator, RequestOptions options = null); diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index db8e2e691..f8642b96c 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -148,7 +148,7 @@ namespace Discord.Rest Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); - Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) + Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) => Task.FromResult(null); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 73028362f..d4dee9f0a 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -56,7 +56,7 @@ namespace Discord.Rest => ClientHelper.GetConnectionsAsync(this, options); /// - public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) + public Task GetInviteAsync(string inviteId, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, options); /// @@ -131,8 +131,8 @@ namespace Discord.Rest async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) - => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) { diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index be6f3795d..a7d590b42 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -55,7 +55,7 @@ namespace Discord.WebSocket public Task> GetConnectionsAsync(RequestOptions options = null) => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); /// - public Task GetInviteAsync(string inviteId, bool withCount = false, RequestOptions options = null) + public Task GetInviteAsync(string inviteId, RequestOptions options = null) => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); // IDiscordClient @@ -70,8 +70,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync(options).ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) - => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 26d9363eb..e29cc4057 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -328,8 +328,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) - => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 412552d74..9efc7d3fa 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1821,8 +1821,8 @@ namespace Discord.WebSocket async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) => await GetConnectionsAsync().ConfigureAwait(false); - async Task IDiscordClient.GetInviteAsync(string inviteId, bool withCount, RequestOptions options) - => await GetInviteAsync(inviteId, withCount, options).ConfigureAwait(false); + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); From aadd6f639f7cdfa0abc9133eb279c88fa22144cf Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sat, 21 Jul 2018 01:07:29 +0100 Subject: [PATCH 065/106] Add missing fields to new MemberInfo type (#1107) --- .../AuditLogs/DataTypes/MemberInfo.cs | 18 +++++++++++ .../DataTypes/MemberUpdateAuditLogData.cs | 32 +++++++++++++------ 2 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberInfo.cs diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberInfo.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberInfo.cs new file mode 100644 index 000000000..b0a1a8e5a --- /dev/null +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberInfo.cs @@ -0,0 +1,18 @@ +namespace Discord.Rest +{ + public struct MemberInfo + { + internal MemberInfo(string nick, bool? deaf, bool? mute, string avatar_hash) + { + Nickname = nick; + Deaf = deaf; + Mute = mute; + AvatarHash = avatar_hash; + } + + public string Nickname { get; } + public bool? Deaf { get; } + public bool? Mute { get; } + public string AvatarHash { get; } + } +} diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 38f078848..40a3ba681 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -8,28 +8,42 @@ namespace Discord.Rest { public class MemberUpdateAuditLogData : IAuditLogData { - private MemberUpdateAuditLogData(IUser target, string newNick, string oldNick) + private MemberUpdateAuditLogData(IUser target, MemberInfo before, MemberInfo after) { Target = target; - NewNick = newNick; - OldNick = oldNick; + Before = before; + After = after; } internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { - var changes = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "nick"); + var changes = entry.Changes; - var newNick = changes.NewValue?.ToObject(); - var oldNick = changes.OldValue?.ToObject(); + var nickModel = changes.FirstOrDefault(x => x.ChangedProperty == "nick"); + var deafModel = changes.FirstOrDefault(x => x.ChangedProperty == "deaf"); + var muteModel = changes.FirstOrDefault(x => x.ChangedProperty == "mute"); + var avatarModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); + + string oldNick = nickModel?.OldValue?.ToObject(), + newNick = nickModel?.NewValue?.ToObject(); + bool? oldDeaf = deafModel?.OldValue?.ToObject(), + newDeaf = deafModel?.NewValue?.ToObject(); + bool? oldMute = muteModel?.OldValue?.ToObject(), + newMute = muteModel?.NewValue?.ToObject(); + string oldAvatar = avatarModel?.OldValue?.ToObject(), + newAvatar = avatarModel?.NewValue?.ToObject(); var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); var user = RestUser.Create(discord, targetInfo); - return new MemberUpdateAuditLogData(user, newNick, oldNick); + var before = new MemberInfo(oldNick, oldDeaf, oldMute, oldAvatar); + var after = new MemberInfo(newNick, newDeaf, newMute, newAvatar); + + return new MemberUpdateAuditLogData(user, before, after); } public IUser Target { get; } - public string NewNick { get; } - public string OldNick { get; } + public MemberInfo Before { get; } + public MemberInfo After { get; } } } From afc3a9d0636598e6638a06781b86f48bac5c5376 Mon Sep 17 00:00:00 2001 From: Casino Boyale Date: Sat, 21 Jul 2018 01:31:30 +0100 Subject: [PATCH 066/106] Added GetJumpUrl() as an extension method for IMessage (#1102) * Added GetJumpUrl() as an IMessage extension method * Removed extra line for consistency * Moved the namespace from Discord.Commands to Discord * lint: remove eof newline * lint: use TextChannel to get GuildID --- .../Extensions/MessageExtensions.cs | 4 ++-- src/Discord.Net.Core/Extensions/MessageExtensions.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Core/Extensions/MessageExtensions.cs diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index 096b03f6b..a27c5f322 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord.Commands { @@ -43,4 +43,4 @@ namespace Discord.Commands return false; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Extensions/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs new file mode 100644 index 000000000..c53ef9053 --- /dev/null +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public static class MessageExtensions + { + public static string GetJumpUrl(this IMessage msg) + { + var channel = msg.Channel; + return $"https://discordapp.com/channels/{(channel is IDMChannel ? "@me" : $"{(channel as ITextChannel).GuildId}")}/{channel.Id}/{msg.Id}"; + } + } +} From 5dad0fa1a1f40c104cec86dde6d1c10a3025d915 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Wed, 1 Aug 2018 16:10:21 +0200 Subject: [PATCH 067/106] Minor fixes around OnModuleBuilding (#1116) * Don't attempt instantiation of an abstract module * Attempt associating a TypeReader in case one is registered late (ie. OnModuleBuilding) --- .../Builders/ModuleBuilder.cs | 2 +- .../Builders/ParameterBuilder.cs | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 0ada5a9c2..6dc50db31 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -120,7 +120,7 @@ namespace Discord.Commands.Builders if (Name == null) Name = _aliases[0]; - if (TypeInfo != null) + if (TypeInfo != null && !TypeInfo.IsAbstract) { var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, services); moduleInstance.OnModuleBuilding(service, this); diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index d1782d7ea..8a59c247c 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -45,14 +45,7 @@ namespace Discord.Commands.Builders internal void SetType(Type type) { - var readers = Command.Module.Service.GetTypeReaders(type); - if (readers != null) - TypeReader = readers.FirstOrDefault().Value; - else - TypeReader = Command.Module.Service.GetDefaultTypeReader(type); - - if (TypeReader == null) - throw new InvalidOperationException($"{type} does not have a TypeReader registered for it. Parameter: {Name} in {Command.PrimaryAlias}"); + TypeReader = GetReader(type); if (type.GetTypeInfo().IsValueType) DefaultValue = Activator.CreateInstance(type); @@ -60,7 +53,16 @@ namespace Discord.Commands.Builders type = ParameterType.GetElementType(); ParameterType = type; } - + + private TypeReader GetReader(Type type) + { + var readers = Command.Module.Service.GetTypeReaders(type); + if (readers != null) + return readers.FirstOrDefault().Value; + else + return Command.Module.Service.GetDefaultTypeReader(type); + } + public ParameterBuilder WithSummary(string summary) { Summary = summary; @@ -100,10 +102,10 @@ namespace Discord.Commands.Builders internal ParameterInfo Build(CommandInfo info) { - if (TypeReader == null) + if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null) throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); return new ParameterInfo(this, info, Command.Module.Service); } } -} \ No newline at end of file +} From c1d78189e11969c4ff9054bcd29323a3ffef7a54 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 1 Aug 2018 15:09:46 -0400 Subject: [PATCH 068/106] core: add PrioritySpeaker to Permissions --- .../Entities/Permissions/ChannelPermission.cs | 3 ++- .../Entities/Permissions/ChannelPermissions.cs | 11 +++++++++-- .../Entities/Permissions/GuildPermission.cs | 1 + .../Entities/Permissions/GuildPermissions.cs | 11 +++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index 740b6c30b..0fbd22c4e 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -30,6 +30,7 @@ namespace Discord DeafenMembers = 0x00_80_00_00, MoveMembers = 0x01_00_00_00, UseVAD = 0x02_00_00_00, + PrioritySpeaker = 0x00_00_01_00, // More General ManageRoles = 0x10_00_00_00, diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index fa2dfb576..61d588f8a 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets a ChannelPermissions that grants all permissions for text channels. public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for voice channels. - public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010000_010001); + public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010100_010001); /// Gets a ChannelPermissions that grants all permissions for category channels. public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); /// Gets a ChannelPermissions that grants all permissions for direct message channels. @@ -78,6 +78,8 @@ namespace Discord 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 => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); + /// If True, a user may use priority speaker in a voice channel. + public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); /// If True, a user may adjust role permissions. This also implictly grants all other permissions. public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); @@ -106,6 +108,7 @@ namespace Discord bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, + bool? prioritySpeaker = null, bool? manageRoles = null, bool? manageWebhooks = null) { @@ -129,6 +132,7 @@ namespace Discord 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, prioritySpeaker, ChannelPermission.PrioritySpeaker); Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); @@ -155,11 +159,12 @@ namespace Discord bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, + bool prioritySpeaker = false, bool manageRoles = false, bool manageWebhooks = false) : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, manageRoles, manageWebhooks) { } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. @@ -182,6 +187,7 @@ namespace Discord bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, + bool? prioritySpeaker = null, bool? manageRoles = null, bool? manageWebhooks = null) => new ChannelPermissions(RawValue, @@ -203,6 +209,7 @@ namespace Discord deafenMembers, moveMembers, useVoiceActivation, + prioritySpeaker, manageRoles, manageWebhooks); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index e90b4269e..13a9e32b1 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -35,6 +35,7 @@ namespace Discord DeafenMembers = 0x00_80_00_00, MoveMembers = 0x01_00_00_00, UseVAD = 0x02_00_00_00, + PrioritySpeaker = 0x00_00_01_00, // General 2 ChangeNickname = 0x04_00_00_00, diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 7704a62d6..c9cb90ec8 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a GuildPermissions that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110011_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110111_111111); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } @@ -69,6 +69,8 @@ namespace Discord 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 => Permissions.GetValue(RawValue, GuildPermission.UseVAD); + /// If True, a user may use priority speaker in a voice channel. + public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); /// If True, a user may change their own nickname. public bool ChangeNickname => Permissions.GetValue(RawValue, GuildPermission.ChangeNickname); @@ -108,6 +110,7 @@ namespace Discord bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, + bool? prioritySpeaker = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, @@ -139,6 +142,7 @@ namespace Discord 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, prioritySpeaker, GuildPermission.PrioritySpeaker); Permissions.SetValue(ref value, changeNickname, GuildPermission.ChangeNickname); Permissions.SetValue(ref value, manageNicknames, GuildPermission.ManageNicknames); Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); @@ -173,6 +177,7 @@ namespace Discord bool deafenMembers = false, bool moveMembers = false, bool useVoiceActivation = false, + bool prioritySpeaker = false, bool changeNickname = false, bool manageNicknames = false, bool manageRoles = false, @@ -203,6 +208,7 @@ namespace Discord deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, + prioritySpeaker: prioritySpeaker, changeNickname: changeNickname, manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, @@ -234,6 +240,7 @@ namespace Discord bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, + bool? prioritySpeaker = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, @@ -242,7 +249,7 @@ namespace Discord => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, - useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + useVoiceActivation, prioritySpeaker, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); From 2c3e9193c29c31a6822a24d7d574d6f827e95805 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 9 Aug 2018 18:26:38 +0200 Subject: [PATCH 069/106] Implement ISnowflakeEntity for Audit Log entries --- src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs | 4 ++-- src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs index b85730a1d..150c59a42 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,7 +9,7 @@ namespace Discord /// /// Represents an entry in an audit log /// - public interface IAuditLogEntry : IEntity + public interface IAuditLogEntry : ISnowflakeEntity { /// /// The action which occured to create this entry diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index 9e30a5014..d01e964ae 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -26,6 +27,8 @@ namespace Discord.Rest return new RestAuditLogEntry(discord, fullLog, model, user); } + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// public ActionType Action { get; } /// From 748e92bcda7410f6a0891b33f15fd7b77fb93a24 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 9 Aug 2018 02:20:36 +0200 Subject: [PATCH 070/106] Allow `FromError(Exception)` on all IResult types. --- src/Discord.Net.Commands/Results/ParseResult.cs | 5 ++++- src/Discord.Net.Commands/Results/PreconditionGroupResult.cs | 5 ++++- src/Discord.Net.Commands/Results/PreconditionResult.cs | 5 ++++- src/Discord.Net.Commands/Results/SearchResult.cs | 5 ++++- src/Discord.Net.Commands/Results/TypeReaderResult.cs | 2 ++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index d4a9af521..3a0692b2d 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -53,6 +54,8 @@ namespace Discord.Commands public static ParseResult FromError(CommandError error, string reason) => new ParseResult(null, null, error, reason); + public static ParseResult FromError(Exception ex) + => FromError(CommandError.Exception, ex.Message); public static ParseResult FromError(IResult result) => new ParseResult(null, null, result.Error, result.ErrorReason); diff --git a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs index 1d7f29122..ee650600a 100644 --- a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -18,6 +19,8 @@ namespace Discord.Commands => new PreconditionGroupResult(null, null, null); public static PreconditionGroupResult FromError(string reason, ICollection preconditions) => new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); + public static new PreconditionGroupResult FromError(Exception ex) + => new PreconditionGroupResult(CommandError.Exception, ex.Message, null); public static new PreconditionGroupResult FromError(IResult result) //needed? => new PreconditionGroupResult(result.Error, result.ErrorReason, null); diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs index ca65a373e..01fc1a3fd 100644 --- a/src/Discord.Net.Commands/Results/PreconditionResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace Discord.Commands { @@ -20,6 +21,8 @@ namespace Discord.Commands => new PreconditionResult(null, null); public static PreconditionResult FromError(string reason) => new PreconditionResult(CommandError.UnmetPrecondition, reason); + public static PreconditionResult FromError(Exception ex) + => new PreconditionResult(CommandError.Exception, ex.Message); public static PreconditionResult FromError(IResult result) => new PreconditionResult(result.Error, result.ErrorReason); diff --git a/src/Discord.Net.Commands/Results/SearchResult.cs b/src/Discord.Net.Commands/Results/SearchResult.cs index 87d900d4d..6a5878ea2 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; namespace Discord.Commands @@ -26,6 +27,8 @@ namespace Discord.Commands => new SearchResult(text, commands, null, null); public static SearchResult FromError(CommandError error, string reason) => new SearchResult(null, null, error, reason); + public static SearchResult FromError(Exception ex) + => FromError(CommandError.Exception, ex.Message); public static SearchResult FromError(IResult result) => new SearchResult(null, null, result.Error, result.ErrorReason); diff --git a/src/Discord.Net.Commands/Results/TypeReaderResult.cs b/src/Discord.Net.Commands/Results/TypeReaderResult.cs index 639ca3ac1..e696dbc17 100644 --- a/src/Discord.Net.Commands/Results/TypeReaderResult.cs +++ b/src/Discord.Net.Commands/Results/TypeReaderResult.cs @@ -50,6 +50,8 @@ namespace Discord.Commands => new TypeReaderResult(values, null, null); public static TypeReaderResult FromError(CommandError error, string reason) => new TypeReaderResult(null, error, reason); + public static TypeReaderResult FromError(Exception ex) + => FromError(CommandError.Exception, ex.Message); public static TypeReaderResult FromError(IResult result) => new TypeReaderResult(null, result.Error, result.ErrorReason); From db90eab9534967a9b4a43436991711a372aec320 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Tue, 21 Aug 2018 06:27:50 +0800 Subject: [PATCH 071/106] Update README.md (#1117) * Initial commit * Add alternative suggestion * Fix typo * Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index bd0ef20c7..7dc8cd788 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,9 @@ Our stable builds available from NuGet through the Discord.Net metapackage: The individual components may also be installed from NuGet: - [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) - [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) -- [Discord.Net.Rpc](https://www.nuget.org/packages/Discord.Net.Rpc/) - [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) - [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) -The following provider is available for platforms not supporting .NET Standard 1.3: -- [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) - ### Unstable (MyGet) Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). @@ -41,5 +37,4 @@ The .NET Core workload must be selected during Visual Studio installation. ## Known Issues ### WebSockets (Win7 and earlier) -.NET Core 1.1 does not support WebSockets on Win7 and earlier. It's recommended to use the Discord.Net.Providers.WS4Net package until this is resolved. -Track the issue [here](https://github.com/dotnet/corefx/issues/9503). +.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. From 4259b8cb0cfb63e66cde9d0f2c3e7de5a59971ab Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 00:11:55 -0400 Subject: [PATCH 072/106] Update CommandAttribute.cs Nullable is not valid for Attributes, this is my suggested fix. --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a0fcf3e4a..a9df3b409 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -17,5 +17,10 @@ namespace Discord.Commands { Text = text; } + public CommandAttribute(string text, bool ignoreExtraArgs) + { + Text = text; + IgnoreExtraArgs = ignoreExtraArgs; + } } } From c56fff9fe55648cdb09325bf95bd244a125f0adc Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 00:44:35 -0400 Subject: [PATCH 073/106] Update CommandAttribute.cs --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a9df3b409..ac3ba599e 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands { public string Text { get; } public RunMode RunMode { get; set; } = RunMode.Default; - public bool? IgnoreExtraArgs { get; set; } + public bool? IgnoreExtraArgs { get; private set; } public CommandAttribute() { From 143fb2808bcf034a1cbb4231fc19424972a22ca4 Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 17:39:00 -0400 Subject: [PATCH 074/106] Update CommandAttribute.cs --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index ac3ba599e..bfc04641a 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands { public string Text { get; } public RunMode RunMode { get; set; } = RunMode.Default; - public bool? IgnoreExtraArgs { get; private set; } + public bool? IgnoreExtraArgs { get; } public CommandAttribute() { From efdb4f926698bbf6ef2d93779291f7f39d675b3f Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 29 Aug 2018 17:11:26 +0100 Subject: [PATCH 075/106] Pass our json serializer to ToObject calls (#1133) Should fix issues in audit logs when deserializing overwrites and similar types which we use a custom contract resolver for. --- .../API/Common/AuditLogOptions.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 22 +++++----- .../DataTypes/ChannelCreateAuditLogData.cs | 8 ++-- .../DataTypes/ChannelDeleteAuditLogData.cs | 6 +-- .../DataTypes/ChannelUpdateAuditLogData.cs | 16 ++++---- .../DataTypes/EmoteCreateAuditLogData.cs | 2 +- .../DataTypes/EmoteDeleteAuditLogData.cs | 2 +- .../DataTypes/EmoteUpdateAuditLogData.cs | 4 +- .../DataTypes/GuildUpdateAuditLogData.cs | 40 +++++++++---------- .../DataTypes/InviteCreateAuditLogData.cs | 14 +++---- .../DataTypes/InviteDeleteAuditLogData.cs | 14 +++---- .../DataTypes/InviteUpdateAuditLogData.cs | 20 +++++----- .../DataTypes/MemberRoleAuditLogData.cs | 2 +- .../DataTypes/MemberUpdateAuditLogData.cs | 16 ++++---- .../DataTypes/OverwriteCreateAuditLogData.cs | 8 ++-- .../DataTypes/OverwriteDeleteAuditLogData.cs | 12 +++--- .../DataTypes/OverwriteUpdateAuditLogData.cs | 12 +++--- .../DataTypes/RoleCreateAuditLogData.cs | 10 ++--- .../DataTypes/RoleDeleteAuditLogData.cs | 10 ++--- .../DataTypes/RoleUpdateAuditLogData.cs | 20 +++++----- .../DataTypes/WebhookCreateAuditLogData.cs | 6 +-- .../DataTypes/WebhookDeleteAuditLogData.cs | 8 ++-- .../DataTypes/WebhookUpdateAuditLogData.cs | 14 +++---- 23 files changed, 132 insertions(+), 136 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs index 65b401cce..24141d90c 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -20,7 +20,7 @@ namespace Discord.API [JsonProperty("role_name")] public string OverwriteRoleName { get; set; } [JsonProperty("type")] - public string OverwriteType { get; set; } + public PermissionTarget OverwriteType { get; set; } [JsonProperty("id")] public ulong? OverwriteTargetId { get; set; } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 2236dbbf8..a9fc1ed74 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -43,9 +43,11 @@ namespace Discord.API public TokenType AuthTokenType { get; private set; } internal string AuthToken { get; private set; } internal IRestClient RestClient { get; private set; } - internal ulong? CurrentUserId { get; set;} + internal ulong? CurrentUserId { get; set; } - public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, + internal JsonSerializer Serializer => _serializer; + + public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) { _restClientProvider = restClientProvider; @@ -235,7 +237,7 @@ namespace Discord.API internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); @@ -414,7 +416,7 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); } - + //Channel Messages public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -490,7 +492,7 @@ namespace Discord.API if (args.Content?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); - + return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) @@ -737,7 +739,7 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - + return await SendJsonAsync("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteGuildAsync(ulong guildId, RequestOptions options = null) @@ -964,7 +966,7 @@ namespace Discord.API { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - + return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } @@ -1163,7 +1165,7 @@ namespace Discord.API int limit = args.Limit.GetValueOrDefault(int.MaxValue); ulong afterGuildId = args.AfterGuildId.GetValueOrDefault(0); - + return await SendAsync>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); } public async Task GetMyApplicationAsync(RequestOptions options = null) @@ -1263,7 +1265,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - + if (AuthTokenType == TokenType.Webhook) return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}", args, new BucketIds(), options: options).ConfigureAwait(false); else @@ -1393,7 +1395,7 @@ namespace Discord.API int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); string fieldName = GetFieldName(methodArgs[argId + 1]); int? mappedId; - + mappedId = BucketIds.GetIndex(fieldName); if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs index ef4787295..51e72c414 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -26,18 +26,16 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var type = typeModel.NewValue.ToObject(); - var name = nameModel.NewValue.ToObject(); + var type = typeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.NewValue.ToObject(discord.ApiClient.Serializer); foreach (var overwrite in overwritesModel.NewValue) { var deny = overwrite.Value("deny"); - var _type = overwrite.Value("type"); + var permType = overwrite.Value("type"); var id = overwrite.Value("id"); var allow = overwrite.Value("allow"); - PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; - overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs index 4816ce770..7af5ca10c 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -27,11 +27,11 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var overwrites = overwritesModel.OldValue.ToObject() + var overwrites = overwritesModel.OldValue.ToObject(discord.ApiClient.Serializer) .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) .ToList(); - var type = typeModel.OldValue.ToObject(); - var name = nameModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.OldValue.ToObject(discord.ApiClient.Serializer); var id = entry.TargetId.Value; return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index 491cb5717..36fe82084 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -23,14 +23,14 @@ namespace Discord.Rest var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - string oldTopic = topicModel?.OldValue?.ToObject(), - newTopic = topicModel?.NewValue?.ToObject(); - int? oldBitrate = bitrateModel?.OldValue?.ToObject(), - newBitrate = bitrateModel?.NewValue?.ToObject(); - int? oldLimit = userLimitModel?.OldValue?.ToObject(), - newLimit = userLimitModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldTopic = topicModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newTopic = topicModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldBitrate = bitrateModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newBitrate = bitrateModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldLimit = userLimitModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newLimit = userLimitModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit); var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs index 5d1ef8463..dac2d90ef 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -21,7 +21,7 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var emoteName = change.NewValue?.ToObject(); + var emoteName = change.NewValue?.ToObject(discord.ApiClient.Serializer); return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs index d0a11191f..73cb31af9 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -17,7 +17,7 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var emoteName = change.OldValue?.ToObject(); + var emoteName = change.OldValue?.ToObject(discord.ApiClient.Serializer); return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs index 60020bcaa..84898013d 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -18,8 +18,8 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var newName = change.NewValue?.ToObject(); - var oldName = change.OldValue?.ToObject(); + var newName = change.NewValue?.ToObject(discord.ApiClient.Serializer); + var oldName = change.OldValue?.ToObject(discord.ApiClient.Serializer); return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs index 08550ed7a..09c1eda18 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -28,26 +28,26 @@ namespace Discord.Rest var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); - int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(), - newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(); - DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(), - newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(); - ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(), - newAfkChannelId = afkChannelModel?.NewValue?.ToObject(); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - string oldRegionId = regionIdModel?.OldValue?.ToObject(), - newRegionId = regionIdModel?.NewValue?.ToObject(); - string oldIconHash = iconHashModel?.OldValue?.ToObject(), - newIconHash = iconHashModel?.NewValue?.ToObject(); - VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(), - newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(); - ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(), - newOwnerId = ownerIdModel?.NewValue?.ToObject(); - MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(), - newMfaLevel = mfaLevelModel?.NewValue?.ToObject(); - int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(), - newContentFilter = contentFilterModel?.NewValue?.ToObject(); + int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAfkChannelId = afkChannelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldRegionId = regionIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newRegionId = regionIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldIconHash = iconHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newIconHash = iconHashModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newOwnerId = ownerIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMfaLevel = mfaLevelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newContentFilter = contentFilterModel?.NewValue?.ToObject(discord.ApiClient.Serializer); IUser oldOwner = null; if (oldOwnerId != null) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs index 292715420..1d7f48e93 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -30,13 +30,13 @@ namespace Discord.Rest var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - var maxAge = maxAgeModel.NewValue.ToObject(); - var code = codeModel.NewValue.ToObject(); - var temporary = temporaryModel.NewValue.ToObject(); - var inviterId = inviterIdModel.NewValue.ToObject(); - var channelId = channelIdModel.NewValue.ToObject(); - var uses = usesModel.NewValue.ToObject(); - var maxUses = maxUsesModel.NewValue.ToObject(); + var maxAge = maxAgeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var code = codeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var temporary = temporaryModel.NewValue.ToObject(discord.ApiClient.Serializer); + var inviterId = inviterIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var channelId = channelIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var uses = usesModel.NewValue.ToObject(discord.ApiClient.Serializer); + var maxUses = maxUsesModel.NewValue.ToObject(discord.ApiClient.Serializer); var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); var inviter = RestUser.Create(discord, inviterInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs index 1dc6d518b..091285532 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -30,13 +30,13 @@ namespace Discord.Rest var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - var maxAge = maxAgeModel.OldValue.ToObject(); - var code = codeModel.OldValue.ToObject(); - var temporary = temporaryModel.OldValue.ToObject(); - var inviterId = inviterIdModel.OldValue.ToObject(); - var channelId = channelIdModel.OldValue.ToObject(); - var uses = usesModel.OldValue.ToObject(); - var maxUses = maxUsesModel.OldValue.ToObject(); + var maxAge = maxAgeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var code = codeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var temporary = temporaryModel.OldValue.ToObject(discord.ApiClient.Serializer); + var inviterId = inviterIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var channelId = channelIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var uses = usesModel.OldValue.ToObject(discord.ApiClient.Serializer); + var maxUses = maxUsesModel.OldValue.ToObject(discord.ApiClient.Serializer); var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); var inviter = RestUser.Create(discord, inviterInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs index b932cfbfc..35088be98 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -23,16 +23,16 @@ namespace Discord.Rest var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(), - newMaxAge = maxAgeModel?.NewValue?.ToObject(); - string oldCode = codeModel?.OldValue?.ToObject(), - newCode = codeModel?.NewValue?.ToObject(); - bool? oldTemporary = temporaryModel?.OldValue?.ToObject(), - newTemporary = temporaryModel?.NewValue?.ToObject(); - ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(), - newChannelId = channelIdModel?.NewValue?.ToObject(); - int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(), - newMaxUses = maxUsesModel?.NewValue?.ToObject(); + int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMaxAge = maxAgeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldCode = codeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newCode = codeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldTemporary = temporaryModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newTemporary = temporaryModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newChannelId = channelIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMaxUses = maxUsesModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses); var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 3bcbce440..48adb1833 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -19,7 +19,7 @@ namespace Discord.Rest { var changes = entry.Changes; - var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(discord.ApiClient.Serializer), (model, role) => new { model.ChangedProperty, Role = role }) .Select(x => new MemberRoleEditInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) .ToList(); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 40a3ba681..96d34610e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -24,14 +24,14 @@ namespace Discord.Rest var muteModel = changes.FirstOrDefault(x => x.ChangedProperty == "mute"); var avatarModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - string oldNick = nickModel?.OldValue?.ToObject(), - newNick = nickModel?.NewValue?.ToObject(); - bool? oldDeaf = deafModel?.OldValue?.ToObject(), - newDeaf = deafModel?.NewValue?.ToObject(); - bool? oldMute = muteModel?.OldValue?.ToObject(), - newMute = muteModel?.NewValue?.ToObject(); - string oldAvatar = avatarModel?.OldValue?.ToObject(), - newAvatar = avatarModel?.NewValue?.ToObject(); + string oldNick = nickModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newNick = nickModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldDeaf = deafModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newDeaf = deafModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldMute = muteModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMute = muteModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldAvatar = avatarModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAvatar = avatarModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); var user = RestUser.Create(discord, targetInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs index d58488136..b13f4b8fd 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -19,17 +19,15 @@ namespace Discord.Rest var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var deny = denyModel.NewValue.ToObject(); - var allow = allowModel.NewValue.ToObject(); + var deny = denyModel.NewValue.ToObject(discord.ApiClient.Serializer); + var allow = allowModel.NewValue.ToObject(discord.ApiClient.Serializer); var permissions = new OverwritePermissions(allow, deny); var id = entry.Options.OverwriteTargetId.Value; var type = entry.Options.OverwriteType; - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; - - return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); + return new OverwriteCreateAuditLogData(new Overwrite(id, type, permissions)); } public Overwrite Overwrite { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 445c2e302..5d5177d9a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -27,14 +27,12 @@ namespace Discord.Rest var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var deny = denyModel.OldValue.ToObject(); - var type = typeModel.OldValue.ToObject(); - var id = idModel.OldValue.ToObject(); - var allow = allowModel.OldValue.ToObject(); + var deny = denyModel.OldValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var id = idModel.OldValue.ToObject(discord.ApiClient.Serializer); + var allow = allowModel.OldValue.ToObject(discord.ApiClient.Serializer); - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; - - return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + return new OverwriteDeleteAuditLogData(new Overwrite(id, type, new OverwritePermissions(allow, deny))); } public Overwrite Overwrite { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs index d000146c3..d05e1feff 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -22,17 +22,17 @@ namespace Discord.Rest var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var beforeAllow = allowModel?.OldValue?.ToObject(); - var afterAllow = allowModel?.NewValue?.ToObject(); - var beforeDeny = denyModel?.OldValue?.ToObject(); - var afterDeny = denyModel?.OldValue?.ToObject(); + var beforeAllow = allowModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var afterAllow = allowModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var beforeDeny = denyModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var afterDeny = denyModel?.OldValue?.ToObject(discord.ApiClient.Serializer); var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); - PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; + var type = entry.Options.OverwriteType; - return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, type); } public OverwritePermissions OldPermissions { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs index dcc1c6ab6..69e72fdb0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -23,11 +23,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.NewValue?.ToObject(); - bool? mentionable = mentionableModel?.NewValue?.ToObject(); - bool? hoist = hoistModel?.NewValue?.ToObject(); - string name = nameModel?.NewValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(); + uint? colorRaw = colorModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? mentionable = mentionableModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? hoist = hoistModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string name = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(discord.ApiClient.Serializer); Color? color = null; GuildPermissions? permissions = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs index 263909daf..f812567cb 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -23,11 +23,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.OldValue?.ToObject(); - bool? mentionable = mentionableModel?.OldValue?.ToObject(); - bool? hoist = hoistModel?.OldValue?.ToObject(); - string name = nameModel?.OldValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(); + uint? colorRaw = colorModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + bool? mentionable = mentionableModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + bool? hoist = hoistModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + string name = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer); Color? color = null; GuildPermissions? permissions = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs index b645ef7ae..5cea865f1 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -24,16 +24,16 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? oldColorRaw = colorModel?.OldValue?.ToObject(), - newColorRaw = colorModel?.NewValue?.ToObject(); - bool? oldMentionable = mentionableModel?.OldValue?.ToObject(), - newMentionable = mentionableModel?.NewValue?.ToObject(); - bool? oldHoist = hoistModel?.OldValue?.ToObject(), - newHoist = hoistModel?.NewValue?.ToObject(); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(), - newPermissionsRaw = permissionsModel?.OldValue?.ToObject(); + uint? oldColorRaw = colorModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newColorRaw = colorModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldMentionable = mentionableModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMentionable = mentionableModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldHoist = hoistModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newHoist = hoistModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newPermissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer); Color? oldColor = null, newColor = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs index 1ae45fb8c..06932bfc4 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -23,9 +23,9 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var channelId = channelIdModel.NewValue.ToObject(); - var type = typeModel.NewValue.ToObject(); - var name = nameModel.NewValue.ToObject(); + var channelId = channelIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.NewValue.ToObject(discord.ApiClient.Serializer); var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs index 4133d5dff..8fc4da578 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -29,10 +29,10 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - var channelId = channelIdModel.OldValue.ToObject(); - var type = typeModel.OldValue.ToObject(); - var name = nameModel.OldValue.ToObject(); - var avatarHash = avatarHashModel?.OldValue?.ToObject(); + var channelId = channelIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.OldValue.ToObject(discord.ApiClient.Serializer); + var avatarHash = avatarHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer); return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs index 54da42a8b..ad7db53e2 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -26,18 +26,18 @@ namespace Discord.Rest var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - var oldName = nameModel?.OldValue?.ToObject(); - var oldChannelId = channelIdModel?.OldValue?.ToObject(); - var oldAvatar = avatarHashModel?.OldValue?.ToObject(); + var oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var oldChannelId = channelIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var oldAvatar = avatarHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer); var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); - var newName = nameModel?.NewValue?.ToObject(); - var newChannelId = channelIdModel?.NewValue?.ToObject(); - var newAvatar = avatarHashModel?.NewValue?.ToObject(); + var newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var newChannelId = channelIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var newAvatar = avatarHashModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var after = new WebhookInfo(newName, newChannelId, newAvatar); var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); - var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + var webhook = webhookInfo != null ? RestWebhook.Create(discord, (IGuild)null, webhookInfo) : null; return new WebhookUpdateAuditLogData(webhook, before, after); } From 2de6cef18c495aaa0cb70b1496ea528b47337fd2 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 30 Aug 2018 14:27:37 -0700 Subject: [PATCH 076/106] Add validation to bot tokens based on string length (#1128) * Add input validation for bot tokens based on their length * Add token validation to BaseDiscordClient#LoginAsync Adds a TokenUtils class which is used to validate that tokens are correct * Revert changes to DiscordRestApiClient * Add Unit tests to the TokenUtils class, fix a logic error that was caught by those tests * Allow for API to throw exceptions Moves the validation of tokens to be inside of LoginInternalAsync, and writes a Warning to the console when the supplied tokens are invalid --- src/Discord.Net.Core/Utils/TokenUtils.cs | 46 ++++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 19 +++- test/Discord.Net.Tests/Tests.TokenUtils.cs | 124 +++++++++++++++++++++ 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Core/Utils/TokenUtils.cs create mode 100644 test/Discord.Net.Tests/Tests.TokenUtils.cs diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs new file mode 100644 index 000000000..2cc0f1041 --- /dev/null +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public static class TokenUtils + { + /// + /// Checks the validity of the supplied token of a specific type. + /// + /// The type of token to validate. + /// The token value to validate. + /// Thrown when the supplied token string is null, empty, or contains only whitespace. + /// Thrown when the supplied TokenType or token value is invalid. + public static void ValidateToken(TokenType tokenType, string token) + { + // A Null or WhiteSpace token of any type is invalid. + if (string.IsNullOrWhiteSpace(token)) + throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token)); + + switch (tokenType) + { + case TokenType.Webhook: + // no validation is performed on Webhook tokens + break; + case TokenType.Bearer: + // no validation is performed on Bearer tokens + break; + case TokenType.Bot: + // bot tokens are assumed to be at least 59 characters in length + // this value was determined by referencing examples in the discord documentation, and by comparing with + // pre-existing tokens + if (token.Length < 59) + throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token)); + break; + default: + // All unrecognized TokenTypes (including User tokens) are considered to be invalid. + throw new ArgumentException("Unrecognized TokenType.", nameof(token)); + } + } + + } +} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index f8642b96c..8ac4e9f98 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -55,11 +55,11 @@ namespace Discord.Rest await _stateLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternalAsync(tokenType, token).ConfigureAwait(false); + await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); } finally { _stateLock.Release(); } } - private async Task LoginInternalAsync(TokenType tokenType, string token) + private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) { if (_isFirstLogin) { @@ -73,6 +73,21 @@ namespace Discord.Rest try { + // If token validation is enabled, validate the token and let it throw any ArgumentExceptions + // that result from invalid parameters + if (validateToken) + { + try + { + TokenUtils.ValidateToken(tokenType, token); + } + catch (ArgumentException ex) + { + // log these ArgumentExceptions and allow for the client to attempt to log in anyways + await LogManager.WarningAsync("Discord", "A supplied token was invalid", ex).ConfigureAwait(false); + } + } + await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; diff --git a/test/Discord.Net.Tests/Tests.TokenUtils.cs b/test/Discord.Net.Tests/Tests.TokenUtils.cs new file mode 100644 index 000000000..dc5a93e34 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.TokenUtils.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Discord +{ + public class TokenUtilsTests + { + /// + /// Tests the usage of + /// to see that when a null, empty or whitespace-only string is passed as the token, + /// it will throw an ArgumentNullException. + /// + [Theory] + [InlineData(null)] + [InlineData("")] // string.Empty isn't a constant type + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\t")] + public void TestNullOrWhitespaceToken(string token) + { + // an ArgumentNullException should be thrown, regardless of the TokenType + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bearer, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Webhook, token)); + } + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [Theory] + [InlineData("123123123")] + // bot token + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + // bearer token taken from discord docs + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestWebhookTokenDoesNotThrowExceptions(string token) + { + TokenUtils.ValidateToken(TokenType.Webhook, token); + } + + // No tests for invalid webhook token behavior, because there is nothing there yet. + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [Theory] + [InlineData("123123123")] + // bot token + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + // bearer token taken from discord docs + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestBearerTokenDoesNotThrowExceptions(string token) + { + TokenUtils.ValidateToken(TokenType.Bearer, token); + } + + // No tests for invalid bearer token behavior, because there is nothing there yet. + + /// + /// Tests the behavior of + /// to see that valid Bot tokens do not throw Exceptions. + /// Valid Bot tokens can be strings of length 59 or above. + /// + [Theory] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + [InlineData("This appears to be completely invalid, however the current validation rules are not very strict.")] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + public void TestBotTokenDoesNotThrowExceptions(string token) + { + // This example token is pulled from the Discord Docs + // https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header + // should not throw any exception + TokenUtils.ValidateToken(TokenType.Bot, token); + } + + /// + /// Tests the usage of with + /// a Bot token that is invalid. + /// + [Theory] + [InlineData("This is invalid")] + // missing a single character from the end + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")] + // bearer token + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestBotTokenInvalidThrowsArgumentException(string token) + { + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + } + + /// + /// Tests the behavior of + /// to see that an is thrown when an invalid + /// is supplied as a parameter. + /// + /// + /// The type is treated as an invalid . + /// + [Theory] + // TokenType.User + [InlineData(0)] + // out of range TokenType + [InlineData(4)] + [InlineData(7)] + public void TestUnrecognizedTokenType(int type) + { + Assert.Throws(() => + TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")); + } + } +} From a2d8800115c7cc41d82871a68cd08fcdecc747ab Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 30 Aug 2018 23:29:05 +0200 Subject: [PATCH 077/106] Add == support on Color (#1126) --- src/Discord.Net.Core/Entities/Roles/Color.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 0bb04d339..727049dcc 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -100,6 +100,17 @@ namespace Discord (uint)(b * 255.0f); } + public static bool operator ==(Color lhs, Color rhs) + => lhs.RawValue == rhs.RawValue; + + public static bool operator !=(Color lhs, Color rhs) + => lhs.RawValue != rhs.RawValue; + + public override bool Equals(object obj) + => (obj is Color c && RawValue == c.RawValue); + + public override int GetHashCode() => RawValue.GetHashCode(); + #if NETSTANDARD2_0 || NET45 public static implicit operator StandardColor(Color color) => StandardColor.FromArgb((int)color.RawValue); From 82cfdffc6516e17ea86013e550060bb6df59a016 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Fri, 31 Aug 2018 05:36:44 +0800 Subject: [PATCH 078/106] Add various optimizations and cleanups (#1114) * Change all Select(... as ...) to OfType * Add changes according to https://github.com/Still34/Discord.Net/commit/194a8aa4273caa4ea6723e6fab4d8b3f9efc0a00 --- .../Attributes/OverrideTypeReaderAttribute.cs | 4 ++-- .../Builders/ModuleClassBuilder.cs | 6 ++--- src/Discord.Net.Commands/CommandService.cs | 4 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 4 ++-- src/Discord.Net.Commands/Map/CommandMap.cs | 4 ++-- .../Map/CommandMapNode.cs | 11 +++++----- src/Discord.Net.Commands/PrimitiveParsers.cs | 8 +++---- .../Readers/TimeSpanTypeReader.cs | 5 ++--- .../Readers/UserTypeReader.cs | 4 ++-- .../Utilities/ReflectionUtils.cs | 4 ++-- .../Entities/Messages/EmbedBuilder.cs | 4 ++-- .../Entities/Messages/EmbedImage.cs | 4 ++-- .../Entities/Messages/EmbedThumbnail.cs | 4 ++-- .../Entities/Messages/EmbedVideo.cs | 4 ++-- .../Extensions/DiscordClientExtensions.cs | 4 ++-- src/Discord.Net.Core/Format.cs | 4 ++-- src/Discord.Net.Core/Utils/MentionUtils.cs | 18 +++++++-------- .../Utils/Paging/PagedEnumerator.cs | 6 ++--- src/Discord.Net.Core/Utils/Permissions.cs | 10 ++++----- src/Discord.Net.Core/Utils/Preconditions.cs | 4 ++-- src/Discord.Net.Rest/BaseDiscordClient.cs | 2 +- src/Discord.Net.Rest/ClientHelper.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 ++--- .../Entities/Channels/RestDMChannel.cs | 2 +- .../Entities/Channels/RestGroupChannel.cs | 2 +- .../Entities/Channels/RestGuildChannel.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 6 ++--- .../Entities/Guilds/RestUserGuild.cs | 4 ++-- .../Entities/Users/RestGuildUser.cs | 4 ++-- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 4 ++-- .../Net/Queue/ClientBucket.cs | 14 ++++++------ .../Net/Queue/RequestQueue.cs | 6 ++--- src/Discord.Net.Rest/Net/RateLimitInfo.cs | 2 +- .../Audio/AudioClient.cs | 18 +++++++-------- .../Audio/Streams/BufferedWriteStream.cs | 10 ++++----- src/Discord.Net.WebSocket/ClientState.cs | 4 ++-- .../DiscordShardedClient.cs | 8 +++---- .../DiscordSocketApiClient.cs | 2 +- .../DiscordSocketClient.cs | 22 +++++++++---------- .../DiscordVoiceApiClient.cs | 4 ++-- .../Entities/Channels/SocketDMChannel.cs | 2 +- .../Entities/Channels/SocketGroupChannel.cs | 6 ++--- .../Entities/Guilds/SocketGuild.cs | 10 ++++----- .../Entities/Messages/MessageCache.cs | 4 ++-- .../Entities/Messages/SocketUserMessage.cs | 2 +- .../Net/DefaultWebSocketClient.cs | 2 +- 46 files changed, 131 insertions(+), 135 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 44ab6d214..17e2310d4 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -7,13 +7,13 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class OverrideTypeReaderAttribute : Attribute { - private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); public Type TypeReader { get; } public OverrideTypeReaderAttribute(Type overridenTypeReader) { - if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) + if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); TypeReader = overridenTypeReader; diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cbe02aafb..307874ca6 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -10,7 +10,7 @@ namespace Discord.Commands { internal static class ModuleClassBuilder { - private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); + private static readonly TypeInfo ModuleTypeInfo = typeof(IModuleBase).GetTypeInfo(); public static async Task> SearchAsync(Assembly assembly, CommandService service) { @@ -135,7 +135,7 @@ namespace Discord.Commands if (builder.Name == null) builder.Name = typeInfo.Name; - var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); + var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition); foreach (var method in validCommands) { @@ -299,7 +299,7 @@ namespace Discord.Commands private static bool IsValidModuleDefinition(TypeInfo typeInfo) { - return _moduleTypeInfo.IsAssignableFrom(typeInfo) && + return ModuleTypeInfo.IsAssignableFrom(typeInfo) && !typeInfo.IsAbstract && !typeInfo.ContainsGenericParameters; } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 6fd5d38ad..7b7cffda2 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -118,7 +118,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (_typedModuleDefs.ContainsKey(type)) - throw new ArgumentException($"This module has already been added."); + throw new ArgumentException("This module has already been added."); var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); @@ -241,7 +241,7 @@ namespace Discord.Commands { if (_defaultTypeReaders.ContainsKey(type)) _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + - $"To suppress this message, use AddTypeReader(reader, true)."); + "To suppress this message, use AddTypeReader(reader, true)."); AddTypeReader(type, reader, true); } /// diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index df0bb297b..604bfbc93 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -1,4 +1,4 @@ -using Discord.Commands.Builders; +using Discord.Commands.Builders; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -63,7 +63,7 @@ namespace Discord.Commands Attributes = builder.Attributes.ToImmutableArray(); Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; + HasVarArgs = builder.Parameters.Count > 0 && builder.Parameters[builder.Parameters.Count - 1].IsMultiple; IgnoreExtraArgs = builder.IgnoreExtraArgs; _action = builder.Callback; diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index bcff800d3..141ec6fdf 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord.Commands { @@ -6,7 +6,7 @@ namespace Discord.Commands { private readonly CommandService _service; private readonly CommandMapNode _root; - private static readonly string[] _blankAliases = new[] { "" }; + private static readonly string[] BlankAliases = { "" }; public CommandMap(CommandService service) { diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 863409207..bd3067718 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -7,7 +7,7 @@ namespace Discord.Commands { internal class CommandMapNode { - private static readonly char[] _whitespaceChars = new[] { ' ', '\r', '\n' }; + private static readonly char[] WhitespaceChars = { ' ', '\r', '\n' }; private readonly ConcurrentDictionary _nodes; private readonly string _name; @@ -52,7 +52,6 @@ namespace Discord.Commands public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) { int nextSegment = NextSegment(text, index, service._separatorChar); - string name; lock (_lockObj) { @@ -60,13 +59,13 @@ namespace Discord.Commands _commands = _commands.Remove(command); else { + string name; if (nextSegment == -1) name = text.Substring(index); else name = text.Substring(index, nextSegment - index); - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) + if (_nodes.TryGetValue(name, out var nextNode)) { nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); if (nextNode.IsEmpty) @@ -100,7 +99,7 @@ namespace Discord.Commands } //Check if this is the last command segment before args - nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); + nextSegment = NextSegment(text, index, WhitespaceChars, service._separatorChar); if (nextSegment != -1) { name = text.Substring(index, nextSegment - index); diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index bf0622c28..e9b6aac3f 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -8,9 +8,9 @@ namespace Discord.Commands internal static class PrimitiveParsers { - private static readonly Lazy> _parsers = new Lazy>(CreateParsers); + private static readonly Lazy> Parsers = new Lazy>(CreateParsers); - public static IEnumerable SupportedTypes = _parsers.Value.Keys; + public static IEnumerable SupportedTypes = Parsers.Value.Keys; static IReadOnlyDictionary CreateParsers() { @@ -34,7 +34,7 @@ namespace Discord.Commands return parserBuilder.ToImmutable(); } - public static TryParseDelegate Get() => (TryParseDelegate)_parsers.Value[typeof(T)]; - public static Delegate Get(Type type) => _parsers.Value[type]; + public static TryParseDelegate Get() => (TryParseDelegate)Parsers.Value[typeof(T)]; + public static Delegate Get(Type type) => Parsers.Value[type]; } } diff --git a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs index 31ab9d821..314fbb322 100644 --- a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -6,8 +6,7 @@ namespace Discord.Commands { internal class TimeSpanTypeReader : TypeReader { - private static readonly string[] _formats = new[] - { + private static readonly string[] Formats = { "%d'd'%h'h'%m'm'%s's'", //4d3h2m1s "%d'd'%h'h'%m'm'", //4d3h2m "%d'd'%h'h'%s's'", //4d3h 1s @@ -27,7 +26,7 @@ namespace Discord.Commands public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - return (TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan)) + return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) ? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 425c2ccb7..498a214e4 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -71,8 +71,8 @@ namespace Discord.Commands .Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase)) .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) - AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); + foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) + AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); } if (results.Count > 0) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 30dd7c36b..ec981cf52 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -8,7 +8,7 @@ namespace Discord.Commands { internal static class ReflectionUtils { - private static readonly TypeInfo _objectTypeInfo = typeof(object).GetTypeInfo(); + private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo(); internal static T CreateObject(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) => CreateBuilder(typeInfo, commands)(services); @@ -54,7 +54,7 @@ namespace Discord.Commands private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) { var result = new List(); - while (ownerType != _objectTypeInfo) + while (ownerType != ObjectTypeInfo) { foreach (var prop in ownerType.DeclaredProperties) { diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 04f4f6884..7e288ef6b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -239,7 +239,7 @@ namespace Discord get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); _name = value; } @@ -251,7 +251,7 @@ namespace Discord set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _value = stringValue; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index f21d42c0c..e12ffc53f 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -20,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 209a93e37..9b3d6153a 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -20,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index f00681d89..5725e0e14 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -18,6 +18,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs index ff3c7caf7..81cd10b49 100644 --- a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs +++ b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs @@ -12,12 +12,12 @@ namespace Discord public static async Task GetDMChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; public static async Task> GetDMChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); public static async Task GetGroupChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; public static async Task> GetGroupChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); public static async Task GetOptimalVoiceRegionAsync(this IDiscordClient discord) { diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index aa822f99e..414e00a29 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,9 +1,9 @@ -namespace Discord +namespace Discord { public static class Format { // Characters which need escaping - private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; + private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 6c69827b4..f2afe497a 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Text; @@ -139,22 +139,22 @@ namespace Discord if (user != null) return $"@{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: if (user != null) return $"{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.FullName: if (user != null) return $"@{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.FullNameNoPrefix: if (user != null) return $"{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.Sanitize: if (guildUser != null && guildUser.Nickname == null) return MentionUser($"{SanitizeChar}{tag.Key}", false); @@ -176,13 +176,13 @@ namespace Discord if (channel != null) return $"#{channel.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (channel != null) return $"{channel.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionChannel($"{SanitizeChar}{tag.Key}"); } @@ -201,13 +201,13 @@ namespace Discord if (role != null) return $"@{role.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (role != null) return $"{role.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionRole($"{SanitizeChar}{tag.Key}"); } diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index 96059bb4f..a31721875 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -60,7 +60,7 @@ namespace Discord if (Current.Count == 0) _info.Remaining = 0; } - _info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; + _info.PageSize = _info.Remaining != null ? Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; if (_info.Remaining != 0) { @@ -74,4 +74,4 @@ namespace Discord public void Dispose() { Current = null; } } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 04e6784c3..6e7125ab7 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace Discord { @@ -119,13 +119,11 @@ namespace Discord resolvedPermissions = mask; //Owners and administrators always have all permissions else { - OverwritePermissions? perms; - //Start with this user's guild permissions resolvedPermissions = guildPermissions; //Give/Take Everyone permissions - perms = channel.GetPermissionOverwrite(guild.EveryoneRole); + var perms = channel.GetPermissionOverwrite(guild.EveryoneRole); if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; @@ -133,7 +131,7 @@ namespace Discord ulong deniedPermissions = 0UL, allowedPermissions = 0UL; foreach (var roleId in user.RoleIds) { - IRole role = null; + IRole role; if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) { perms = channel.GetPermissionOverwrite(role); @@ -151,7 +149,7 @@ namespace Discord if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; - if (channel is ITextChannel textChannel) + if (channel is ITextChannel) { if (!GetValue(resolvedPermissions, ChannelPermission.ViewChannel)) { diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 300f584e4..1a297a6a1 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -198,7 +198,7 @@ namespace Discord for (var i = 0; i < roles.Length; i++) { if (roles[i] == guildId) - throw new ArgumentException($"The everyone role cannot be assigned to a user", name); + throw new ArgumentException("The everyone role cannot be assigned to a user", name); } } } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 8ac4e9f98..8a3db3e6a 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -92,7 +92,7 @@ namespace Discord.Rest await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } - catch (Exception) + catch { await LogoutInternalAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index d8f481d15..aa99fe005 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -47,7 +47,7 @@ namespace Discord.Rest public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); - return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); + return models.Select(RestConnection.Create).ToImmutableArray(); } public static async Task GetInviteAsync(BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a9fc1ed74..f8e3e6f50 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -125,7 +125,7 @@ namespace Discord.API LoginState = LoginState.LoggedIn; } - catch (Exception) + catch { await LogoutInternalAsync().ConfigureAwait(false); throw; @@ -1394,9 +1394,9 @@ namespace Discord.API int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); string fieldName = GetFieldName(methodArgs[argId + 1]); - int? mappedId; - mappedId = BucketIds.GetIndex(fieldName); + var mappedId = BucketIds.GetIndex(fieldName); + if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 64efcf24b..20a76aaf0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable + public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel { public RestUser CurrentUser { get; private set; } public RestUser Recipient { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 5ac0f2c40..c6b5d6ad0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -11,7 +11,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable + public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel { private string _iconId; private ImmutableDictionary _users; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 7355e3673..1dbcf7e9a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -7,7 +7,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable + public class RestGuildChannel : RestChannel, IGuildChannel { private ImmutableArray _overwrites; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 9bf13fa07..e6819e8a4 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -168,7 +168,7 @@ namespace Discord.Rest public async Task> GetTextChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestTextChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task GetVoiceChannelAsync(ulong id, RequestOptions options = null) { @@ -178,12 +178,12 @@ namespace Discord.Rest public async Task> GetVoiceChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestVoiceChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task> GetCategoryChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestCategoryChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task GetAFKChannelAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index 6bc9cea7a..de5a5f7d9 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; @@ -6,7 +6,7 @@ using Model = Discord.API.UserGuild; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestUserGuild : RestEntity, ISnowflakeEntity, IUserGuild + public class RestUserGuild : RestEntity, IUserGuild { private string _iconId; diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index e571f8f73..3c47587cb 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -9,7 +9,7 @@ using Model = Discord.API.GuildMember; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGuildUser : RestUser, IGuildUser, IUpdateable + public class RestGuildUser : RestUser, IGuildUser { private long? _joinedAtTicks; private ImmutableArray _roleIds; diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index ec789be59..54fe3b681 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -131,14 +131,14 @@ namespace Discord.Net.Rest return new RestResponse(response.StatusCode, headers, stream); } - private static readonly HttpMethod _patch = new HttpMethod("PATCH"); + private static readonly HttpMethod Patch = new HttpMethod("PATCH"); private HttpMethod GetMethod(string method) { switch (method) { case "DELETE": return HttpMethod.Delete; case "GET": return HttpMethod.Get; - case "PATCH": return _patch; + case "PATCH": return Patch; case "POST": return HttpMethod.Post; case "PUT": return HttpMethod.Put; default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"); diff --git a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs index f32df1bcf..cd9d8aa54 100644 --- a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; namespace Discord.Net.Queue { @@ -9,8 +9,8 @@ namespace Discord.Net.Queue } internal struct ClientBucket { - private static readonly ImmutableDictionary _defsByType; - private static readonly ImmutableDictionary _defsById; + private static readonly ImmutableDictionary DefsByType; + private static readonly ImmutableDictionary DefsById; static ClientBucket() { @@ -23,16 +23,16 @@ namespace Discord.Net.Queue var builder = ImmutableDictionary.CreateBuilder(); foreach (var bucket in buckets) builder.Add(bucket.Type, bucket); - _defsByType = builder.ToImmutable(); + DefsByType = builder.ToImmutable(); var builder2 = ImmutableDictionary.CreateBuilder(); foreach (var bucket in buckets) builder2.Add(bucket.Id, bucket); - _defsById = builder2.ToImmutable(); + DefsById = builder2.ToImmutable(); } - public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; - public static ClientBucket Get(string id) => _defsById[id]; + public static ClientBucket Get(ClientBucketType type) => DefsByType[type]; + public static ClientBucket Get(string id) => DefsById[id]; public ClientBucketType Type { get; } public string Id { get; } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 943b76359..40b91e98e 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; #if DEBUG_LIMITS using System.Diagnostics; @@ -16,10 +16,10 @@ namespace Discord.Net.Queue private readonly ConcurrentDictionary _buckets; private readonly SemaphoreSlim _tokenLock; + private readonly CancellationTokenSource _cancelToken; //Dispose token private CancellationTokenSource _clearToken; private CancellationToken _parentToken; private CancellationToken _requestCancelToken; //Parent token + Clear token - private CancellationTokenSource _cancelToken; //Dispose token private DateTimeOffset _waitUntil; private Task _cleanupTask; @@ -115,7 +115,7 @@ namespace Discord.Net.Queue foreach (var bucket in _buckets.Select(x => x.Value)) { if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) - _buckets.TryRemove(bucket.Id, out RequestBucket ignored); + _buckets.TryRemove(bucket.Id, out _); } await Task.Delay(60000, _cancelToken.Token); //Runs each minute } diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index a517f290c..d31cc5cdd 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -15,7 +15,7 @@ namespace Discord.Net internal RateLimitInfo(Dictionary headers) { IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && - bool.TryParse(temp, out var isGlobal) ? isGlobal : false; + bool.TryParse(temp, out var isGlobal) && isGlobal; Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && int.TryParse(temp, out var limit) ? limit : (int?)null; Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index bbb65a298..9f197b5c5 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace Discord.Audio { //TODO: Add audio reconnecting - internal partial class AudioClient : IAudioClient, IDisposable + internal partial class AudioClient : IAudioClient { internal struct StreamPair { @@ -65,7 +65,7 @@ namespace Discord.Audio ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); - ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); + ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedPacket += ProcessPacketAsync; @@ -131,7 +131,7 @@ namespace Discord.Audio await keepaliveTask.ConfigureAwait(false); _keepaliveTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out _)) { } _lastMessageTime = 0; await ClearInputStreamsAsync().ConfigureAwait(false); @@ -292,7 +292,7 @@ namespace Discord.Audio { if (packet.Length != 70) { - await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); return; } string ip; @@ -304,7 +304,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); return; } @@ -331,7 +331,7 @@ namespace Discord.Audio { if (pair.Key == value) { - int latency = (int)(Environment.TickCount - pair.Value); + int latency = Environment.TickCount - pair.Value; int before = UdpLatency; UdpLatency = latency; @@ -344,7 +344,7 @@ namespace Discord.Audio { if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) { - await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); return; } if (!_ssrcMap.TryGetValue(ssrc, out var userId)) @@ -363,7 +363,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); return; } //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); @@ -372,7 +372,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); + await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); return; } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index fb302f132..47a7e2809 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -116,7 +116,7 @@ namespace Discord.Audio.Streams timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync($"Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer underrun"); #endif } } @@ -140,7 +140,7 @@ namespace Discord.Audio.Streams if (!_bufferPool.TryDequeue(out byte[] buffer)) { #if DEBUG - var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock + var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock #endif return; } @@ -149,7 +149,7 @@ namespace Discord.Audio.Streams if (!_isPreloaded && _queuedFrames.Count == _queueLength) { #if DEBUG - var _ = _logger?.DebugAsync($"Preloaded"); + var _ = _logger?.DebugAsync("Preloaded"); #endif _isPreloaded = true; } @@ -169,8 +169,8 @@ namespace Discord.Audio.Streams { do cancelToken.ThrowIfCancellationRequested(); - while (_queuedFrames.TryDequeue(out Frame ignored)); + while (_queuedFrames.TryDequeue(out _)); return Task.Delay(0); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f07976a0a..a119e5e28 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -72,7 +72,7 @@ namespace Discord.WebSocket switch (channel) { case SocketDMChannel dmChannel: - _dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); + _dmChannels.TryRemove(dmChannel.Recipient.Id, out _); break; case SocketGroupChannel groupChannel: _groupChannels.TryRemove(id); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index e29cc4057..881ce3909 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -13,11 +13,11 @@ namespace Discord.WebSocket { private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; + private readonly Dictionary _shardIdsToIndex; + private readonly bool _automaticShards; private int[] _shardIds; - private Dictionary _shardIdsToIndex; private DiscordSocketClient[] _shards; private int _totalShards; - private bool _automaticShards; /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. public override int Latency { get => GetLatency(); protected set { } } @@ -25,8 +25,8 @@ namespace Discord.WebSocket public override IActivity Activity { get => _shards[0].Activity; protected set { } } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); - public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); + public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); + public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); public IReadOnlyCollection Shards => _shards; public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 8ae41cc59..d42bd7132 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -26,9 +26,9 @@ namespace Discord.API public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly bool _isExplicitUrl; private CancellationTokenSource _connectCancelToken; private string _gatewayUrl; - private bool _isExplicitUrl; //Store our decompression streams for zlib shared state private MemoryStream _compressed; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 9efc7d3fa..45d60b764 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -63,9 +63,9 @@ namespace Discord.WebSocket public override IReadOnlyCollection Guilds => State.Guilds; public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; public IReadOnlyCollection DMChannels - => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); public IReadOnlyCollection GroupChannels - => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); /// Creates a new REST/WebSocket discord client. @@ -207,7 +207,7 @@ namespace Discord.WebSocket await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out _)) { } _lastMessageTime = 0; await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); @@ -218,7 +218,7 @@ namespace Discord.WebSocket //Clear large guild queue await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); - while (_largeGuilds.TryDequeue(out ulong guildId)) { } + while (_largeGuilds.TryDequeue(out _)) { } //Raise virtual GUILD_UNAVAILABLEs await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); @@ -351,7 +351,7 @@ namespace Discord.WebSocket var gameModel = new GameModel(); // Discord only accepts rich presence over RPC, don't even bother building a payload - if (Activity is RichGame game) + if (Activity is RichGame) throw new NotSupportedException("Outgoing Rich Presences are not supported"); if (Activity != null) @@ -508,7 +508,7 @@ namespace Discord.WebSocket { type = "GUILD_AVAILABLE"; _lastGuildAvailableTime = Environment.TickCount; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -533,7 +533,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); var guild = AddGuild(data, State); if (guild != null) @@ -614,7 +614,7 @@ namespace Discord.WebSocket if (data.Unavailable == true) { type = "GUILD_UNAVAILABLE"; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -630,7 +630,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); var guild = RemoveGuild(data.Id); if (guild != null) @@ -1626,7 +1626,7 @@ namespace Discord.WebSocket var guild = State.RemoveGuild(id); if (guild != null) { - foreach (var channel in guild.Channels) + foreach (var _ in guild.Channels) State.RemoveChannel(id); foreach (var user in guild.Users) user.GlobalUser.RemoveRef(this); @@ -1679,7 +1679,7 @@ namespace Discord.WebSocket if (eventHandler.HasSubscribers) { if (HandlerTimeout.HasValue) - await TimeoutWrap(name, () => eventHandler.InvokeAsync()).ConfigureAwait(false); + await TimeoutWrap(name, eventHandler.InvokeAsync).ConfigureAwait(false); else await eventHandler.InvokeAsync().ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 0eb92caed..80dec0fd4 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -39,8 +39,8 @@ namespace Discord.Audio private readonly JsonSerializer _serializer; private readonly SemaphoreSlim _connectionLock; + private readonly IUdpSocket _udp; private CancellationTokenSource _connectCancelToken; - private IUdpSocket _udp; private bool _isDisposed; private ulong _nextKeepalive; @@ -188,7 +188,7 @@ namespace Discord.Audio ConnectionState = ConnectionState.Connected; } - catch (Exception) + catch { await DisconnectInternalAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 11b7ce25a..9a8be9703 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -97,7 +97,7 @@ namespace Discord.WebSocket if (id == Recipient.Id) return Recipient; else if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser as SocketSelfUser; + return Discord.CurrentUser; else return null; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 125625456..74a1e3725 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -18,10 +18,10 @@ namespace Discord.WebSocket public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { private readonly MessageCache _messages; + private readonly ConcurrentDictionary _voiceStates; private string _iconId; private ConcurrentDictionary _users; - private ConcurrentDictionary _voiceStates; public string Name { get; private set; } @@ -129,7 +129,7 @@ namespace Discord.WebSocket internal SocketGroupUser GetOrAddUser(UserModel model) { if (_users.TryGetValue(model.Id, out SocketGroupUser user)) - return user as SocketGroupUser; + return user; else { var privateUser = SocketGroupUser.Create(this, Discord.State, model); @@ -143,7 +143,7 @@ namespace Discord.WebSocket if (_users.TryRemove(id, out SocketGroupUser user)) { user.GlobalUser.RemoveRef(Discord); - return user as SocketGroupUser; + return user; } return null; } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index bf33ef569..78ea4004a 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -91,11 +91,11 @@ namespace Discord.WebSocket } } public IReadOnlyCollection TextChannels - => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public IReadOnlyCollection VoiceChannels - => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public IReadOnlyCollection CategoryChannels - => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; public SocketRole EveryoneRole => GetRole(Id); public IReadOnlyCollection Channels @@ -563,7 +563,7 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); } - catch (Exception) + catch { await DisconnectAudioInternalAsync().ConfigureAwait(false); throw; @@ -580,7 +580,7 @@ namespace Discord.WebSocket throw new TimeoutException(); return await promise.Task.ConfigureAwait(false); } - catch (Exception) + catch { await DisconnectAudioAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index c2cad4d86..6ebd0fa1c 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -28,7 +28,7 @@ namespace Discord.WebSocket _orderedMessages.Enqueue(message.Id); while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) - _messages.TryRemove(msgId, out SocketMessage msg); + _messages.TryRemove(msgId, out _); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index f8c15a986..bf817e008 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -12,12 +12,12 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { + private readonly List _reactions = new List(); private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - private List _reactions = new List(); public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index c60368da0..dc5201ac1 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -22,8 +22,8 @@ namespace Discord.Net.WebSockets private readonly SemaphoreSlim _lock; private readonly Dictionary _headers; + private readonly IWebProxy _proxy; private ClientWebSocket _client; - private IWebProxy _proxy; private Task _task; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; From 272604f27578dba67de8039e4e88901a26299883 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 30 Aug 2018 17:38:44 -0400 Subject: [PATCH 079/106] Make voice connection properties optional. (#1129) --- .../Entities/Channels/SocketVoiceChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index f967e6e6a..8e6e09a9f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -43,7 +43,7 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); - public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) + public async Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) { return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); } From 9e9a11d4b09362aadac6d32be0ac83fdddc5e19e Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 6 Sep 2018 19:08:45 -0700 Subject: [PATCH 080/106] Fix swapped parameters in ArgumentException, ArgumentNullException cstrs (#1139) * Fix swapped parameters in ArgumentException and ArgumentNullException cstrs The constructors of ArgumentException and ArgumentNullException are inconsistent with each other, which results in their parameters being swapped here and there. * Use named parameters for ArgumentException constructors Cleans up some of the methods of EmbedBuilder to use simpler syntax as well --- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 4 +- .../Entities/Messages/EmbedBuilder.cs | 34 ++++++++--------- .../Permissions/ChannelPermissions.cs | 2 +- .../Utils/ConcurrentHashSet.cs | 24 ++++++------ src/Discord.Net.Core/Utils/MentionUtils.cs | 6 +-- src/Discord.Net.Core/Utils/Preconditions.cs | 38 +++++-------------- src/Discord.Net.Core/Utils/TokenUtils.cs | 6 +-- src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 +-- .../Entities/Guilds/GuildHelper.cs | 10 ++--- 9 files changed, 56 insertions(+), 74 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index e3a228c83..a71a73327 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; namespace Discord @@ -58,7 +58,7 @@ namespace Discord { if (TryParse(text, out Emote result)) return result; - throw new ArgumentException("Invalid emote format", nameof(text)); + throw new ArgumentException(message: "Invalid emote format", paramName: nameof(text)); } public static bool TryParse(string text, out Emote result) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 7e288ef6b..82e94e39f 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -29,7 +29,7 @@ namespace Discord get => _title; set { - if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); + if (value?.Length > MaxTitleLength) throw new ArgumentException(message: $"Title length must be less than or equal to {MaxTitleLength}.", paramName: nameof(Title)); _title = value; } } @@ -38,7 +38,7 @@ namespace Discord get => _description; set { - if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); + if (value?.Length > MaxDescriptionLength) throw new ArgumentException(message: $"Description length must be less than or equal to {MaxDescriptionLength}.", paramName: nameof(Description)); _description = value; } } @@ -48,7 +48,7 @@ namespace Discord get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(Url)); _url = value; } } @@ -57,7 +57,7 @@ namespace Discord get => _thumbnail?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(ThumbnailUrl)); _thumbnail = new EmbedThumbnail(value, null, null, null); } } @@ -66,7 +66,7 @@ namespace Discord get => _image?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(ImageUrl)); _image = new EmbedImage(value, null, null, null); } } @@ -75,8 +75,8 @@ namespace Discord get => _fields; set { - if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); - if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); + if (value == null) throw new ArgumentNullException(paramName: nameof(Fields), message: "Cannot set an embed builder's fields collection to null"); + if (value.Count > MaxFieldCount) throw new ArgumentException(message: $"Field count must be less than or equal to {MaxFieldCount}.", paramName: nameof(Fields)); _fields = value; } } @@ -200,7 +200,7 @@ namespace Discord { if (Fields.Count >= MaxFieldCount) { - throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(field)); + throw new ArgumentException(message: $"Field count must be less than or equal to {MaxFieldCount}.", paramName: nameof(field)); } Fields.Add(field); @@ -239,8 +239,8 @@ namespace Discord get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); - if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(message: "Field name must not be null, empty or entirely whitespace.", paramName: nameof(Name)); + if (value.Length > MaxFieldNameLength) throw new ArgumentException(message: $"Field name length must be less than or equal to {MaxFieldNameLength}.", paramName: nameof(Name)); _name = value; } } @@ -251,8 +251,8 @@ namespace Discord set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); - if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException(message: "Field value must not be null or empty.", paramName: nameof(Value)); + if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException(message: $"Field value length must be less than or equal to {MaxFieldValueLength}.", paramName: nameof(Value)); _value = stringValue; } } @@ -290,7 +290,7 @@ namespace Discord get => _name; set { - if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); + if (value?.Length > MaxAuthorNameLength) throw new ArgumentException(message: $"Author name length must be less than or equal to {MaxAuthorNameLength}.", paramName: nameof(Name)); _name = value; } } @@ -299,7 +299,7 @@ namespace Discord get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(Url)); _url = value; } } @@ -308,7 +308,7 @@ namespace Discord get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); _iconUrl = value; } } @@ -345,7 +345,7 @@ namespace Discord get => _text; set { - if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); + if (value?.Length > MaxFooterTextLength) throw new ArgumentException(message: $"Footer text length must be less than or equal to {MaxFooterTextLength}.", paramName: nameof(Text)); _text = value; } } @@ -354,7 +354,7 @@ namespace Discord get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); _iconUrl = value; } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 61d588f8a..ea56734ff 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -29,7 +29,7 @@ namespace Discord case ICategoryChannel _: return Category; case IDMChannel _: return DM; case IGroupChannel _: return Group; - default: throw new ArgumentException("Unknown channel type", nameof(channel)); + default: throw new ArgumentException(message: "Unknown channel type", paramName: nameof(channel)); } } diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 1fc11587e..e9edfb772 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -160,23 +160,23 @@ namespace Discord public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) : this(comparer) { - if (collection == null) throw new ArgumentNullException(nameof(collection)); + if (collection == null) throw new ArgumentNullException(paramName: 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)); + if (collection == null) throw new ArgumentNullException(paramName: nameof(collection)); + if (comparer == null) throw new ArgumentNullException(paramName: 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 (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(paramName: nameof(concurrencyLevel)); + if (capacity < 0) throw new ArgumentOutOfRangeException(paramName: nameof(capacity)); + if (comparer == null) throw new ArgumentNullException(paramName: nameof(comparer)); if (capacity < concurrencyLevel) capacity = concurrencyLevel; @@ -197,7 +197,7 @@ namespace Discord { foreach (var value in collection) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) throw new ArgumentException(); @@ -209,7 +209,7 @@ namespace Discord public bool ContainsKey(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return ContainsKeyInternal(value, _comparer.GetHashCode(value)); } private bool ContainsKeyInternal(T value, int hashcode) @@ -232,7 +232,7 @@ namespace Discord public bool TryAdd(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return TryAddInternal(value, _comparer.GetHashCode(value), true); } private bool TryAddInternal(T value, int hashcode, bool acquireLock) @@ -281,7 +281,7 @@ namespace Discord public bool TryRemove(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return TryRemoveInternal(value); } private bool TryRemoveInternal(T value) @@ -467,4 +467,4 @@ namespace Discord Monitor.Exit(_tables._locks[i]); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index f2afe497a..a081b5a5a 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -21,7 +21,7 @@ namespace Discord { if (TryParseUser(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided user mention string. public static bool TryParseUser(string text, out ulong userId) @@ -45,7 +45,7 @@ namespace Discord { if (TryParseChannel(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided channel mention string. public static bool TryParseChannel(string text, out ulong channelId) @@ -66,7 +66,7 @@ namespace Discord { if (TryParseRole(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided role mention string. public static bool TryParseRole(string text, out ulong roleId) diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 1a297a6a1..6cbf4a173 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -10,8 +10,8 @@ namespace Discord private static ArgumentNullException CreateNotNullException(string name, string msg) { - if (msg == null) return new ArgumentNullException(name); - else return new ArgumentNullException(name, msg); + if (msg == null) return new ArgumentNullException(paramName: name); + else return new ArgumentNullException(paramName: name, message: msg); } //Strings @@ -45,10 +45,7 @@ namespace Discord } private static ArgumentException CreateNotEmptyException(string name, string msg) - { - if (msg == null) return new ArgumentException("Argument cannot be blank", name); - else return new ArgumentException(name, msg); - } + => new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name); //Numerics public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } @@ -85,11 +82,8 @@ namespace Discord public static void NotEqual(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } private static ArgumentException CreateNotEqualException(string name, string msg, T value) - { - if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); - else return new ArgumentException(msg, name); - } - + => new ArgumentException(message: msg ?? $"Value may not be equal to {value}", paramName: name); + public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } public static void AtLeast(byte obj, byte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } public static void AtLeast(short obj, short value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } @@ -108,10 +102,7 @@ namespace Discord public static void AtLeast(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } private static ArgumentException CreateAtLeastException(string name, string msg, T value) - { - if (msg == null) return new ArgumentException($"Value must be at least {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be at least {value}", paramName: name); public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } public static void GreaterThan(byte obj, byte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } @@ -131,10 +122,7 @@ namespace Discord public static void GreaterThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } private static ArgumentException CreateGreaterThanException(string name, string msg, T value) - { - if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be greater than {value}", paramName: name); public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } public static void AtMost(byte obj, byte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } @@ -154,10 +142,7 @@ namespace Discord public static void AtMost(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } private static ArgumentException CreateAtMostException(string name, string msg, T value) - { - if (msg == null) return new ArgumentException($"Value must be at most {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be at most {value}", paramName: name); public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } public static void LessThan(byte obj, byte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } @@ -177,10 +162,7 @@ namespace Discord public static void LessThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } private static ArgumentException CreateLessThanException(string name, string msg, T value) - { - if (msg == null) return new ArgumentException($"Value must be less than {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be less than {value}", paramName: name); // Bulk Delete public static void YoungerThanTwoWeeks(ulong[] collection, string name) @@ -198,7 +180,7 @@ namespace Discord for (var i = 0; i < roles.Length; i++) { if (roles[i] == guildId) - throw new ArgumentException("The everyone role cannot be assigned to a user", name); + throw new ArgumentException(message: "The everyone role cannot be assigned to a user", paramName: name); } } } diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs index 2cc0f1041..6decdc52f 100644 --- a/src/Discord.Net.Core/Utils/TokenUtils.cs +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -19,7 +19,7 @@ namespace Discord { // A Null or WhiteSpace token of any type is invalid. if (string.IsNullOrWhiteSpace(token)) - throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token)); + throw new ArgumentNullException(paramName: nameof(token), message: "A token cannot be null, empty, or contain only whitespace."); switch (tokenType) { @@ -34,11 +34,11 @@ namespace Discord // this value was determined by referencing examples in the discord documentation, and by comparing with // pre-existing tokens if (token.Length < 59) - throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token)); + throw new ArgumentException(message: "A Bot token must be at least 59 characters in length.", paramName: nameof(token)); break; default: // All unrecognized TokenTypes (including User tokens) are considered to be invalid. - throw new ArgumentException("Unrecognized TokenType.", nameof(token)); + throw new ArgumentException(message: "Unrecognized TokenType.", paramName: nameof(token)); } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f8e3e6f50..02529310f 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -78,7 +78,7 @@ namespace Discord.API case TokenType.Bearer: return $"Bearer {token}"; default: - throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); + throw new ArgumentException(message: "Unknown OAuth token type", paramName: nameof(tokenType)); } } internal virtual void Dispose(bool disposing) @@ -473,7 +473,7 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); @@ -490,7 +490,7 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); options = RequestOptions.CreateOrClone(options); return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 4bd0e9972..3b2935149 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -147,7 +147,7 @@ namespace Discord.Rest public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var props = new TextChannelProperties(); func?.Invoke(props); @@ -164,7 +164,7 @@ namespace Discord.Rest public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var props = new VoiceChannelProperties(); func?.Invoke(props); @@ -181,7 +181,7 @@ namespace Discord.Rest public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var args = new CreateGuildChannelParams(name, ChannelType.Category); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); @@ -221,7 +221,7 @@ namespace Discord.Rest public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); var role = RestRole.Create(client, guild, model); @@ -356,7 +356,7 @@ namespace Discord.Rest public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, RequestOptions options) { - if (func == null) throw new ArgumentNullException(nameof(func)); + if (func == null) throw new ArgumentNullException(paramName: nameof(func)); var props = new EmoteProperties(); func(props); From 97d17cfdda22a04173b63247c646acc7cd1122a7 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 11 Sep 2018 18:21:22 -0400 Subject: [PATCH 081/106] api: add slow mode This adds the following property to ITextChannel - SlowMode (int) The SlowMode field defines the time in seconds users must wait between sending messages; if zero, slow-mode is disabled. Bots are not affected by slow-mode, and as such, no changes need to be made to the REST client's internal ratelimiting. This is strictly a read/write cosmetic property for bots. --- .../Entities/Channels/ITextChannel.cs | 3 +++ .../Entities/Channels/TextChannelProperties.cs | 14 +++++++++++++- src/Discord.Net.Rest/API/Common/Channel.cs | 4 +++- .../API/Rest/ModifyTextChannelParams.cs | 4 +++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 ++ .../Entities/Channels/ChannelHelper.cs | 3 ++- .../Entities/Channels/RestTextChannel.cs | 2 ++ .../Entities/Channels/SocketTextChannel.cs | 2 ++ 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 2aa070b03..e9350601d 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -13,6 +13,9 @@ namespace Discord /// Gets the current topic for this text channel. string Topic { get; } + /// Gets the current slow-mode delay for this channel. 0 if disabled. + int SlowMode { get; } + /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); /// Bulk deletes multiple messages. diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index b7b568133..e3c0557dd 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -1,4 +1,6 @@ -namespace Discord +using System; + +namespace Discord { /// public class TextChannelProperties : GuildChannelProperties @@ -11,5 +13,15 @@ /// Should this channel be flagged as NSFW? /// public Optional IsNsfw { get; set; } + /// + /// What the slow-mode ratelimit for this channel should be set to; 0 will disable slow-mode. + /// + /// + /// This value must fall within [0, 120] + /// + /// Users with will be exempt from slow-mode. + /// + /// Throws ArgummentOutOfRange if the value does not fall within [0, 120] + public Optional SlowMode { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 97c35a57b..57a5ce9ab 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -33,6 +33,8 @@ namespace Discord.API public Optional LastPinTimestamp { get; set; } [JsonProperty("nsfw")] public Optional Nsfw { get; set; } + [JsonProperty("rate_limit_per_user")] + public Optional SlowMode { get; set; } //VoiceChannel [JsonProperty("bitrate")] diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 9cabc67c1..7cb1405c9 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,5 +10,7 @@ namespace Discord.API.Rest public Optional Topic { get; set; } [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } + [JsonProperty("rate_limit_per_user")] + public Optional SlowMode { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 02529310f..f7e86a8f3 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -356,6 +356,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.AtLeast(args.SlowMode, 0, nameof(args.SlowMode)); + Preconditions.AtMost(args.SlowMode, 120, nameof(args.SlowMode)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 50cb352a7..47f82f612 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -45,7 +45,8 @@ namespace Discord.Rest Position = args.Position, CategoryId = args.CategoryId, Topic = args.Topic, - IsNsfw = args.IsNsfw + IsNsfw = args.IsNsfw, + SlowMode = args.SlowMode, }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 7f57c96ff..8fe17ba2f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -12,6 +12,7 @@ namespace Discord.Rest public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } + public int SlowMode { get; private set; } public ulong? CategoryId { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); @@ -34,6 +35,7 @@ namespace Discord.Rest base.Update(model); CategoryId = model.CategoryId; Topic = model.Topic.Value; + SlowMode = model.SlowMode.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index e41c2ba94..d6982bf6a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,6 +16,7 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } + public int SlowMode { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; @@ -47,6 +48,7 @@ namespace Discord.WebSocket base.Update(state, model); CategoryId = model.CategoryId; Topic = model.Topic.Value; + SlowMode = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? _nsfw = model.Nsfw.GetValueOrDefault(); } From 232f525b59e801332ec9dca755366944c79afa1d Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 11 Sep 2018 18:28:31 -0400 Subject: [PATCH 082/106] lint: refactor SlowMode -> SlowModeInterval this was the name change i always wanted --- src/Discord.Net.Core/Entities/Channels/ITextChannel.cs | 2 +- .../Entities/Channels/TextChannelProperties.cs | 2 +- src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 2 +- .../Entities/Channels/SocketTextChannel.cs | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index e9350601d..89e10e65e 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -14,7 +14,7 @@ namespace Discord string Topic { get; } /// Gets the current slow-mode delay for this channel. 0 if disabled. - int SlowMode { get; } + int SlowModeInterval { get; } /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index e3c0557dd..87adccb85 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -22,6 +22,6 @@ namespace Discord /// Users with will be exempt from slow-mode. /// /// Throws ArgummentOutOfRange if the value does not fall within [0, 120] - public Optional SlowMode { get; set; } + public Optional SlowModeInterval { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 7cb1405c9..94f149fc1 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -11,6 +11,6 @@ namespace Discord.API.Rest [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } [JsonProperty("rate_limit_per_user")] - public Optional SlowMode { get; set; } + public Optional SlowModeInterval { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f7e86a8f3..35fa0e989 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -356,8 +356,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); - Preconditions.AtLeast(args.SlowMode, 0, nameof(args.SlowMode)); - Preconditions.AtMost(args.SlowMode, 120, nameof(args.SlowMode)); + Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval)); + Preconditions.AtMost(args.SlowModeInterval, 120, nameof(args.SlowModeInterval)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 47f82f612..74ce7870f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -46,7 +46,7 @@ namespace Discord.Rest CategoryId = args.CategoryId, Topic = args.Topic, IsNsfw = args.IsNsfw, - SlowMode = args.SlowMode, + SlowModeInterval = args.SlowModeInterval, }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 8fe17ba2f..12437c969 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -12,7 +12,7 @@ namespace Discord.Rest public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } - public int SlowMode { get; private set; } + public int SlowModeInterval { get; private set; } public ulong? CategoryId { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); @@ -35,7 +35,7 @@ namespace Discord.Rest base.Update(model); CategoryId = model.CategoryId; Topic = model.Topic.Value; - SlowMode = model.SlowMode.Value; + SlowModeInterval = model.SlowMode.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 3b2935149..bde36922a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -156,7 +156,7 @@ namespace Discord.Rest { CategoryId = props.CategoryId, Topic = props.Topic, - IsNsfw = props.IsNsfw + IsNsfw = props.IsNsfw, }; var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index d6982bf6a..2e9cd90be 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,7 +16,7 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } - public int SlowMode { get; private set; } + public int SlowModeInterval { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; @@ -48,7 +48,7 @@ namespace Discord.WebSocket base.Update(state, model); CategoryId = model.CategoryId; Topic = model.Topic.Value; - SlowMode = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? + SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? _nsfw = model.Nsfw.GetValueOrDefault(); } From 674a0fc5dd5181e05e32125f63117b2a1a197344 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Fri, 28 Sep 2018 15:19:00 -0700 Subject: [PATCH 083/106] Add Ubuntu target to AppVeyor CI build (#1149) * Target Ubuntu in Appveyor CI build AppVeyor has released support for targetting Ubuntu 18.04 environments. This adds Ubuntu to the list of build images for Appveyor to use. * Set clone folder to use appveyor defaults * Check if appveyor provides appveyor-retry by trying to run it * Minor syntax change * Minor syntax change * Skip using appveyor-retry (for now) * Use forward slash for paths so that it works on both windows and linux * Implement replacement for appveyor-retry * Script cleanup * Remove commented out line * Only package releases on Windows * Add the isWindows condition to the deploy stage --- appveyor.yml | 65 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 54b9a1251..0c0c7b102 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,13 +2,17 @@ version: build-{build} branches: only: - dev -image: Visual Studio 2017 +image: +- Visual Studio 2017 +- Ubuntu nuget: disable_publish_on_pr: true pull_requests: do_not_increment_build_number: true -clone_folder: C:\Projects\Discord.Net +# Use the default clone_folder +# Windows: C:\Projects\discord-net +# Ubuntu: /home/appveyor/projects/discord-net cache: test/Discord.Net.Tests/cache.db environment: @@ -20,23 +24,50 @@ init: - ps: $Env:BUILD = "$($Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0"))" build_script: -- ps: appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" +- ps: >- + if ($isLinux) + { + # AppVeyor Linux images do not have appveyor-retry, which retries the commands a few times + # until the command exits with code 0. + # So, this is done with a short script. + $code = 0 + $counter = 0 + do + { + dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" + $code = $LASTEXITCODE + $counter++ + if ($code -ne 0) + { + # Wait 5s before attempting to run again + Start-sleep -Seconds 5 + } + } + until ($counter -eq 5 -or $code -eq 0) + } + else + { + appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" + } - ps: dotnet build Discord.Net.sln -c "Release" /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" after_build: -- ps: dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } - ps: >- - if ($Env:APPVEYOR_REPO_TAG -eq "true") { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" - } else { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" + if ($isWindows) + { + if ($Env:APPVEYOR_REPO_TAG -eq "true") { + nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" + } else { + nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" + } } -- ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } +- ps: if ($isWindows) { Get-ChildItem artifacts/*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } } test_script: - ps: >- @@ -52,10 +83,12 @@ deploy: symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package on: branch: dev + isWindows: True - provider: NuGet server: https://www.myget.org/F/rogueexception/api/v2/package api_key: secure: D+vW2O2LBf/iJb4f+q8fkyIW2VdIYIGxSYLWNrOD4BHlDBZQlJipDbNarWjUr2Kn symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package on: - branch: dev \ No newline at end of file + branch: dev + isWindows: True From efaedac98f306e1d855f6dc6f9efb5dd99eeb554 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sat, 29 Sep 2018 06:20:38 +0800 Subject: [PATCH 084/106] docs: Fix for incomplete structure sample (#1145) --- docs/guides/getting_started/samples/intro/structure.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index a9a018c3a..5165e2fdb 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -36,7 +36,7 @@ class Program // you must set the MessageCacheSize. You may adjust the number as needed. //MessageCacheSize = 50, - // If your platform doesn't have native websockets, + // If your platform doesn't have native WebSockets, // add Discord.Net.Providers.WS4Net from NuGet, // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance @@ -57,7 +57,7 @@ class Program _commands.Log += Log; // Setup your DI container. - _services = ConfigureServices(), + _services = ConfigureServices(); } @@ -116,7 +116,9 @@ class Program await InitCommands(); // Login and connect. - await _client.LoginAsync(TokenType.Bot, /* */); + await _client.LoginAsync(TokenType.Bot, + // < DO NOT HARDCODE YOUR TOKEN > + Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); // Wait infinitely so your bot actually stays connected. @@ -160,7 +162,7 @@ class Program // Execute the command. (result does not indicate a return value, // rather an object stating if the command executed successfully). - var result = await _commands.ExecuteAsync(context, pos); + var result = await _commands.ExecuteAsync(context, pos, _services); // Uncomment the following lines if you want the bot // to send a message if it failed. From c898325e4594c7b0ea6455c715903c775ec42a2a Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 Sep 2018 19:22:48 -0300 Subject: [PATCH 085/106] Fix GetReactionUsersAsync (#1151) --- src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 3dc3e74e9..30ee75c47 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -71,7 +71,7 @@ namespace Discord.Rest }, nextPage: (info, lastPage) => { - if (lastPage.Count != DiscordConfig.MaxUsersPerBatch) + if (lastPage.Count != DiscordConfig.MaxUserReactionsPerBatch) return false; info.Position = lastPage.Max(x => x.Id); From de4d4150533327d29bfb1200835fff6fcff8f50a Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Fri, 28 Sep 2018 18:46:05 -0700 Subject: [PATCH 086/106] Fix deploy conditions in CI script (#1157) * Fix deploy conditions in CI script The deploy conditions had been using a powershell-only variable, and not a environment variable. This meant that the deployment failed on both Windows and Linux (expected only on Linux). * Remove an extra newline * Remove Debug statements from appveyor build script --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0c0c7b102..ef5f1667e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -83,7 +83,7 @@ deploy: symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package on: branch: dev - isWindows: True + CI_WINDOWS: true - provider: NuGet server: https://www.myget.org/F/rogueexception/api/v2/package api_key: @@ -91,4 +91,4 @@ deploy: symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package on: branch: dev - isWindows: True + CI_WINDOWS: true From c9ba79ec80c055b0ccb05e3f9260aa6a0963bdbd Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 1 Oct 2018 05:09:32 +0800 Subject: [PATCH 087/106] Initial fix (#1160) --- src/Discord.Net.Rest/API/Common/InviteVanity.cs | 10 ++++++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Common/InviteVanity.cs diff --git a/src/Discord.Net.Rest/API/Common/InviteVanity.cs b/src/Discord.Net.Rest/API/Common/InviteVanity.cs new file mode 100644 index 000000000..d39792674 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InviteVanity.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class InviteVanity + { + [JsonProperty("code")] + public string Code { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 35fa0e989..249e8e946 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -929,13 +929,13 @@ namespace Discord.API } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) + public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); } public async Task> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index bde36922a..2b7900154 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -213,8 +213,10 @@ namespace Discord.Rest public static async Task GetVanityInviteAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { - var model = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); - return RestInviteMetadata.Create(client, guild, null, model); + var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); + if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); + var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); + return RestInviteMetadata.Create(client, guild, null, inviteModel); } //Roles From 6b21b11f7d2fe4ffb93ae6c394d78bc7c7dcb75b Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 30 Sep 2018 17:10:10 -0400 Subject: [PATCH 088/106] feature: adjust the ratelimit for reactions (#1108) * feature: adjust the ratelimit for reactions allows users to add reactions quickly * fix: don't force DEBUG_LIMITS * fix: undefined behavior using DateTime on intel architerctures it's fine * fix: ensure add/del rxn ends up in the same bucket --- src/Discord.Net.Core/RequestOptions.cs | 3 ++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 10 ++++++++-- src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 5f3a8814b..40f0ebaa4 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; namespace Discord { @@ -22,6 +22,7 @@ namespace Discord internal bool IgnoreState { get; set; } internal string BucketId { get; set; } internal bool IsClientBucket { get; set; } + internal bool IsReactionBucket { get; set; } internal static RequestOptions CreateOrClone(RequestOptions options) { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 249e8e946..1679579b2 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -580,6 +580,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -587,10 +588,13 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/@me", ids, options: options).ConfigureAwait(false); + // @me is non-const to fool the ratelimiter, otherwise it will put add/remove in separate buckets + var me = "@me"; + await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{me}", ids, options: options).ConfigureAwait(false); } public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) { @@ -599,10 +603,12 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{userId}", ids, options: options).ConfigureAwait(false); + var user = CurrentUserId.HasValue ? (userId == CurrentUserId.Value ? "@me" : userId.ToString()) : userId.ToString(); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{user}", ids, options: options).ConfigureAwait(false); } public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index c4f5996c5..d7f9779a4 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -250,6 +250,10 @@ namespace Discord.Net.Queue else if (info.Reset.HasValue) { resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); + + if (request.Options.IsReactionBucket) + resetTick = DateTimeOffset.Now.AddMilliseconds(250); + int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; #if DEBUG_LIMITS Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {info.Lag?.TotalMilliseconds} ms lag)"); From ff0fea98a65d907fbce07856f1a9ef4aebb9108b Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 1 Oct 2018 05:44:33 +0800 Subject: [PATCH 089/106] Documentation Overhaul (#1161) * Add XML docs * Clean up style switcher * Squash commits on branch docs/faq-n-patches * Fix broken theme selector * Add local image embed instruction * Add a bunch of XML docs * Add a bunch of XML docs * Fix broken search + DocFX by default ships with an older version of jQuery, switching to a newer version confuses parts of the DocFX Javascript. * Minor fixes for CONTRIBUTING.md and README.md * Clean up filterConfig.yml + New config exposes Discord.Net namespace since it has several common public exceptions that may be helpful to users * Add XML docs * Read token from Environment Variable instead of hardcode * Add XMLDocs * Compress some assets & add OAuth2 URL generator * Fix sample link & add missing pictures * Add tag examples * Fix embed docs consistency * Add details regarding userbot support * Add XML Docs * Add XML Docs * Add XML Docs * Minor fixes in documentations + Fix unescaped '<' + Fix typo * Fix seealso for preconditions and add missing descriptions * Add missing exceptions * Document exposed TypeReaders * Fix letter-casing for files * Add 'last modified' plugin Source: https://github.com/Still34/DocFx.Plugin.LastModified Licensed under MIT License * XML Docs * Fix minor consistencies & redundant impl * Add properties examples to overwrite * Fix missing Username prop * Add warning for bulk-delete endpoint * Replace note block * Add BaseSocketClient docs * Add XML docs * Replace langword null to code block null instead - Because DocFX sucks at rendering langword * Replace all langword placements with code block * Add more IGuild docs * Add details to SpotifyGame * Initial proofread of the articles * Add explanation for RunMode * Add event docs - MessageReceived - ChannelUpdated/Destroyed/Created * Fix light theme link color * Fix xml docs error * Add partial documentation for audit log impl * Add documentation for some REST-based objects * Add partial documentation for audit log objects * Add more XML comments to quotation mark alias map stuff, including an example * Add reference to CommandServiceConfig from the util docs' * Add explanation that if " is removed then it wont work * Fix missing service provider in example * Add documentation for new INestedChannel * Add documentation * Add documentation for new API version & few events * Revise guide paragraphs/samples + Fix various formatting. + Provide a more detailed walkthrough for dependency injection. + Add C# note at intro. * Fix typos & formatting * Improve group module example * Small amount to see if I'm doing it right * Remove/cleanup redundant variables * Fix EnterTypingState impl for doc inheritance * Fix Test to resolve changes made in 15b58e * Improve precondition documentation + Add precondition usage sample + Add precondition group usage sample + Move precondition samples to its own sample folder * Move samples to individual folders * Clarify token source * Cleanup styling of README.md for docs * Replace InvalidPathChars for NS1.3 * InvalidPathChars does not exist in NS1.3; replaced with GetInvalidPathChars instead. * Add a missing change for 2c7cc738 * Update LastModified to v1.1.0 & add license * Rewrite installation page for Core 2.1 * Fix anchor link * Bump post-processor to v1.1.1 * Add fixes to partial file & add license * Moved theme-switcher code to scripts partial file + Add author's MIT license to featherlight javascript * Remove unused bootstrap plugin * Bump LastModified plugin * Changed the path from 'lastmodified' to 'last-modified' for consistency * Cleanup README & Contribution guide * Changes to last pr * Fix GetCategoryAsync docs * Proofread and cleanup articles * Change passive voice in "Get Started" to active * Fix improper preposition in Commands Introduction page * Fix minor grammar mistakes in "Your First Bot" (future tense -> present tense/subjunctive mood -> indicative mood/proper noun casing/incorrect noun/add missing article) * Fix minor grammar mistakes in "Installation" (missing article) * no hablo ingles * Try try try again * I'm sure you're having as much fun as I am * Cleanup TOC & fix titles * Improve styling + Change title font to Noto Sans + Add materialized design for commit message box * Add DescriptionGenerator plugin * Add nightly section for clarification * Fix typos in Nightlies & Post-execution * Bump DescriptionGenerator to v1.1.0 + This build adds the functionality of generating managed references' summary into the description tag. * Initial emoji article draft * Add 'additional information' section for emoji article * Add cosmetic changes to the master css * Alter info box color + Add transition to article content * Add clarification in the emoji article * Emphasize that normal emoji string will not translate to its Unicode representation. * Clean up or add some of the samples featured in the article. + Add emoji/emote declaration section for clarification. + Add WebSocket emote sample. - Remove inconsistent styling ('wacky memes' proves to be too out of place). * Improve readability for nightlies article * Move 'Bundled Preconditions' section * Bump LastModified to fix UTC DateTime parsing * Add langwordMapping.yml * Add XML docs * Add VSC workspace rule * The root workspace limits the ruler to 120 characters for member documentations and excludes folders such as 'samples' and 'docs'. * The docs workspace limits the ruler to 70 characters for standard conceptual article to comply with documentation's CONTRIBUTING.md rule, and excludes temprorary folders created by DocFX. * Update CONTRIBUTING.md * Add documentation style rule * Fix styling of several member documentation * Fix ' />' caused by Agent Smith oddities * Fix styling to be more specific about the mention of IDs * Fix exception summary to comply with official Microsoft Docs style * References https://docs.microsoft.com/en-us/dotnet/api/system.argumentnullexception?view=netframework-4.7.2 https://docs.microsoft.com/en-us/dotnet/api/system.platformnotsupportedexception?view=netframework-4.7.2 https://docs.microsoft.com/en-us/dotnet/api/system.badimageformatexception?view=netframework-4.7.2 * Add XML documentations * Shift color return docs * Fix minor docs * Added documentation for SocketDMChannel, SocketGuildChannel, and SocketTextChannel * Add XML docs * Corrections to SocketGuildChannel * Corrections to SocketTextChannel * Corrections to SocketDMChannel * Swapped out 'id' for 'snowflake identifier * Swapped out 'id' for 'snowflake identifier' * SocketDMChannel amendments * SocketGuildChannel amendments * SocketTextChannel amendments * Add XML docs & patch return types + Starting from this commit, all return types for tasks will use style similar to most documentations featured on docs.microsoft.com References: https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.-ctor?view=efcore-2.1 https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream.readasync?view=netcore-2.1 https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writelineasync?view=netcore-2.1#System_IO_TextWriter_WriteLineAsync_System_Char___ And many more other asynchronous method documentations featured in the latest BCL. * Added documentation for many audit log data types, fixed vowel indefinite articles * Change audit log data types to start with 'Contains' (verb) instead of an article * Fix some documentation issues and document some more audit log data types * Fix English posession * Add XML doc * Documented two more types * Documented RoleCreateAuditLogData * Document remaining audit log data types * Added RestDMChannel documentation * Added RestGuildChannel documentation * Added RestTextChannel documentation * Added RestVoiceChannel documentation * Added RestUser documentation * Added RestRole documentation * Added RestMessage documentation * Slightly better wording * Contains -> Contains a piece of (describe article) * [EN] Present perf. -> past perf. * Add XML docs * Fix arrow alignment * Clarify supported nullable type * Fixed a typo in ISnowflakeEntity * Added RestUser Documentation * Added RestInvite documentation * Add XML docs & minor optimizations * Minor optimization for doc rendering * Rollback font optimization changes * Amendments to RestUser * Added SocketDMChannel documentation * Added RestDMChannel documentation * Added RestGuild documentation * Adjustment to SocketDMChannel * Added minimal descriptions from the API documentation for Integration types * Added obsolete mention to the ReadMessages flag. * Added remarks about 2FA requirement for guild permissions * Added xmldoc for GuildPermission methods * Added xml doc for ToAllowList and ToDenyList * Added specification of how the bits of the color raw value are packed * Added discord API documentation to IConnection interface * I can spell :^) * Fix whitespace in ChannelPermission * fix spacing of values in guildpermission * Made changes to get field descriptions from feedback, added returns tag to IConnection * Added property get standard for IntegrationAccount * Added property get pattern to xml docs and identical returns tag. * Change all color class references to struct ...because it isn't a class. * Add XML docs * Rewrote the returns tags in IGuildIntegration, removed the ones I was unsure about. * Rewrote the rest of the returns tags * Amendments * Cleanup doc for c1d78189 * Added types to tags where missing * Added second sample for adding reactions * Added some class summaries * Missed a period * Amendments * restored the removed line break * Removed unnecessary see tag * Use consistent quotation marks around subscribers, the name for these users are dependant on the source of where they are integrated from (youtube or twitch), so we should not use a name that is specific to one platform * Add tag to the IGuildIntegration xmldocs * Fix grammar issue * Update DescriptionGenerator * Cleanup of https://github.com/Still34/Discord.Net/pull/8 * Cleanup previous PR * Fix for misleading behaviour in the emoji guide + Original lines stated that sending a emoji wrapped in colon will not be parsed, but that was incorrect; replaced with reactions instead of sending messages as the example * Add strings for dictionary in DotSettings * Add XML docs * Fix lots of typos in comments + Geez, I didn't know there were so many. * Add XML docs & rewrite GetMessagesAsync docs This commit rewrites the remarks section of GetMessagesAsync, as well as adding examples to several methods. * Update 'Your First Bot' + This commit reflects the new changes made to the Discord Application Developer Portal after its major update * Initial optimization for DocFX render & add missing files * Add examples in message methods * Cleanup https://github.com/RogueException/Discord.Net/pull/1128 * Fix first bot note * Cleanup FAQ structure * Add XML docs * Update docfx plugins * Fix navbar collapsing issue * Fix broken xref * Cleanup FAQ section + Add introductory paragraphs to each FAQ section. + Add 'missing dependency' entry to commands FAQ. * Split commands FAQ to 'General' and 'DI' sections. * Cleanup https://github.com/RogueException/Discord.Net/pull/1139 * Fix missing namespace * Add missing highlighting css for the light theme * Add additional clarification for installing packages * Add indentation to example for clarity * Cleanup several articles to be more human-friendly and easier to read * Remove RPC-related notes * Cleanup slow-mode-related documentation strings * Add an additional note about cross-guild emote usage * Add CreateTextChannel sample * Add XMLDocs --- .gitignore | 3 + CONTRIBUTING.md | 20 +- Discord.Net.code-workspace | 23 + Discord.Net.sln.DotSettings | 16 + docs/CONTRIBUTING.md | 39 +- docs/Discord.Net.Docs.code-workspace | 21 + docs/README.md | 19 +- .../Commands/CommandException.Overwrite.md | 31 + .../DontAutoLoadAttribute.Overwrite.md | 22 + .../Commands/DontInjectAttribute.Overwrite.md | 27 + .../Commands/ICommandContext.Inclusion.md | 5 + .../Commands/ICommandContext.Overwrite.md | 27 + .../PreconditionAttribute.Overwrites.md | 103 ++ ...PreconditionAttribute.Remarks.Inclusion.md | 6 + .../Common/EmbedBuilder.Overwrites.md | 68 ++ .../Common/EmbedObjectBuilder.Inclusion.md | 25 + .../Common/EmbedObjectBuilder.Overwrites.md | 20 + docs/_overwrites/Common/IEmote.Inclusion.md | 26 + docs/_overwrites/Common/IEmote.Overwrites.md | 81 ++ .../Common/ObjectProperties.Overwrites.md | 174 +++ .../OverrideTypeReaderAttribute.Overwrites.md | 24 + .../Common/images/embed-example.png | Bin 0 -> 15290 bytes .../Common/images/react-example.png | Bin 0 -> 8994 bytes .../DocFX.Plugin.DescriptionGenerator.dll | Bin 0 -> 9216 bytes .../description-generator/plugins/LICENSE | 21 + docs/_template/last-modified/plugins/LICENSE | 21 + .../plugins/LastModifiedPostProcessor.dll | Bin 0 -> 11776 bytes .../partials/affix.tmpl.partial | 33 + .../partials/head.tmpl.partial | 27 + .../partials/scripts.tmpl.partial | 10 + .../light-dark-theme/styles/cornerify.js | 3 + .../light-dark-theme/styles/dark.css | 304 +++++ .../styles/docfx.vendor.minify.css | 1022 +++++++++++++++++ .../light-dark-theme/styles/gray.css | 311 +++++ .../light-dark-theme/styles/light.css | 113 ++ .../light-dark-theme/styles/master.css | 156 +++ .../styles/plugin-featherlight.js | 37 + .../light-dark-theme/styles/styleswitcher.js | 26 + .../styles/theme-switcher.css | 9 + .../light-dark-theme/styles/tomorrow.css | 72 ++ .../light-dark-theme/styles/vs2015.css | 115 ++ docs/api/index.md | 19 +- docs/docfx.json | 96 +- docs/faq/basics/basic-operations.md | 123 ++ docs/faq/basics/client-basics.md | 66 ++ docs/faq/basics/getting-started.md | 79 ++ docs/faq/basics/images/dev-mode.png | Bin 0 -> 80742 bytes docs/faq/basics/images/mention-escape.png | Bin 0 -> 19611 bytes docs/faq/basics/images/snowflake.png | Bin 0 -> 73062 bytes docs/faq/basics/samples/cast.cs | 15 + docs/faq/basics/samples/emoji-others.cs | 18 + docs/faq/basics/samples/emoji-self.cs | 17 + docs/faq/commands/dependency-injection.md | 54 + docs/faq/commands/general.md | 142 +++ docs/faq/commands/samples/DI.cs | 28 + docs/faq/commands/samples/Remainder.cs | 20 + docs/faq/commands/samples/missing-dep.cs | 29 + .../faq/commands/samples/runmode-cmdattrib.cs | 7 + .../faq/commands/samples/runmode-cmdconfig.cs | 10 + docs/faq/misc/glossary.md | 82 ++ docs/faq/misc/legacy.md | 29 + docs/faq/toc.yml | 18 + docs/filterConfig.yml | 12 +- docs/guides/commands/commands.md | 343 ------ docs/guides/commands/dependency-injection.md | 47 + docs/guides/commands/intro.md | 221 ++++ docs/guides/commands/post-execution.md | 122 ++ docs/guides/commands/preconditions.md | 83 ++ .../commands/samples/command_handler.cs | 63 - .../dependency_map_setup.cs | 62 + .../dependency-injection/dependency_module.cs | 37 + .../dependency_module_noinject.cs | 29 + .../commands/samples/dependency_map_setup.cs | 18 - .../commands/samples/dependency_module.cs | 40 - docs/guides/commands/samples/empty-module.cs | 6 - .../commands/samples/intro/command_handler.cs | 63 + .../commands/samples/intro/empty-module.cs | 8 + .../commands/samples/{ => intro}/groups.cs | 15 +- .../commands/samples/{ => intro}/module.cs | 26 +- .../post-execution/command_exception_log.cs | 13 + .../command_executed_adv_demo.cs | 13 + .../post-execution/command_executed_demo.cs | 38 + .../post-execution/customresult_base.cs | 6 + .../post-execution/customresult_extended.cs | 10 + .../post-execution/customresult_usage.cs | 10 + .../post-execution/post-execution_basic.cs | 11 + .../preconditions/group_precondition.cs | 9 + .../preconditions/precondition_usage.cs | 3 + .../{ => preconditions}/require_owner.cs | 10 +- .../typereaders/typereader-register.cs | 29 + .../samples/{ => typereaders}/typereader.cs | 5 +- docs/guides/commands/typereaders.md | 70 ++ docs/guides/concepts/connections.md | 43 +- docs/guides/concepts/entities.md | 53 +- docs/guides/concepts/events.md | 36 +- docs/guides/concepts/logging.md | 39 +- docs/guides/concepts/samples/connections.cs | 2 +- docs/guides/concepts/samples/entities.cs | 8 +- docs/guides/concepts/samples/events.cs | 2 +- docs/guides/concepts/samples/logging.cs | 2 +- docs/guides/deployment/deployment.md | 86 ++ docs/guides/emoji/emoji.md | 100 ++ docs/guides/emoji/images/emojipedia.png | Bin 0 -> 15043 bytes docs/guides/emoji/images/emote-format.png | Bin 0 -> 14696 bytes .../emoji/images/fileformat-emoji-src.png | Bin 0 -> 23541 bytes docs/guides/emoji/samples/emoji-sample.cs | 6 + docs/guides/emoji/samples/emote-sample.cs | 7 + .../emoji/samples/socket-emote-sample.cs | 11 + docs/guides/getting_started/first-bot.md | 263 +++++ .../images/appveyor-artifacts.png | Bin 0 -> 7002 bytes .../getting_started/images/appveyor-nupkg.png | Bin 0 -> 9412 bytes .../images/install-vs-nuget.png | Bin 119670 -> 49542 bytes .../getting_started/images/intro-add-bot.png | Bin 26232 -> 6417 bytes .../images/intro-authorize.png | Bin 0 -> 19052 bytes .../images/intro-bot-settings.png | Bin 0 -> 5704 bytes .../images/intro-client-id.png | Bin 5109 -> 0 bytes .../images/intro-create-app.png | Bin 52035 -> 0 bytes .../images/intro-create-bot.png | Bin 45743 -> 0 bytes .../images/intro-oauth-settings.png | Bin 0 -> 5655 bytes .../images/intro-public-bot.png | Bin 0 -> 11135 bytes .../images/intro-scopes-bot.png | Bin 0 -> 17423 bytes .../getting_started/images/intro-token.png | Bin 26594 -> 18552 bytes .../images/nightlies-vs-note.png | Bin 0 -> 10162 bytes .../images/nightlies-vs-step1.png | Bin 0 -> 13372 bytes .../images/nightlies-vs-step2.png | Bin 0 -> 13713 bytes .../images/nightlies-vs-step4.png | Bin 0 -> 8214 bytes docs/guides/getting_started/installing.md | 156 ++- docs/guides/getting_started/intro.md | 237 ---- docs/guides/getting_started/nightlies.md | 86 ++ .../samples/first-bot/async-context.cs | 9 + .../samples/first-bot/client.cs | 21 + .../samples/first-bot/complete.cs | 34 + .../samples/first-bot/logging.cs | 5 + .../samples/{intro => first-bot}/message.cs | 2 +- .../samples/{intro => first-bot}/structure.cs | 0 .../samples/intro/async-context.cs | 15 - .../getting_started/samples/intro/client.cs | 17 - .../getting_started/samples/intro/complete.cs | 44 - .../getting_started/samples/intro/logging.cs | 22 - .../getting_started/samples/project.csproj | 13 - .../getting_started/samples/project.xml | 17 + docs/guides/getting_started/terminology.md | 40 +- docs/guides/introduction/intro.md | 54 + docs/guides/migrating/migrating.md | 61 - docs/guides/migrating/samples/event.cs | 4 - docs/guides/migrating/samples/sync_event.cs | 5 - docs/guides/toc.yml | 42 +- docs/guides/voice/sending-voice.md | 5 +- docs/index.md | 31 +- docs/langwordMapping.yml | 61 + docs/toc.yml | 7 +- .../Discord.Net.Rpc/DiscordRpcApiClient.cs | 2 +- .../Discord.Net.Rpc/DiscordRpcConfig.cs | 4 +- .../Entities/Channels/RpcDMChannel.cs | 1 + .../Entities/Channels/RpcGroupChannel.cs | 1 + samples/01_basic_ping_bot/Program.cs | 6 +- samples/02_commands_framework/Program.cs | 2 +- .../Attributes/AliasAttribute.cs | 29 +- .../Attributes/CommandAttribute.cs | 15 + .../Attributes/DontAutoLoadAttribute.cs | 8 + .../Attributes/DontInjectAttribute.cs | 34 +- .../Attributes/GroupAttribute.cs | 11 + .../Attributes/NameAttribute.cs | 10 + .../Attributes/OverrideTypeReaderAttribute.cs | 31 +- .../ParameterPreconditionAttribute.cs | 11 + .../Attributes/PreconditionAttribute.cs | 21 +- .../RequireBotPermissionAttribute.cs | 44 +- .../Preconditions/RequireContextAttribute.cs | 34 +- .../Preconditions/RequireNsfwAttribute.cs | 24 +- .../Preconditions/RequireOwnerAttribute.cs | 33 +- .../RequireUserPermissionAttribute.cs | 47 +- .../Attributes/PriorityAttribute.cs | 12 +- .../Attributes/RemainderAttribute.cs | 3 + .../Attributes/RemarksAttribute.cs | 3 + .../Attributes/SummaryAttribute.cs | 3 + .../Builders/CommandBuilder.cs | 1 + .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/CommandContext.cs | 16 +- src/Discord.Net.Commands/CommandError.cs | 27 +- src/Discord.Net.Commands/CommandException.cs | 13 + src/Discord.Net.Commands/CommandMatch.cs | 5 +- src/Discord.Net.Commands/CommandParser.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 212 +++- .../CommandServiceConfig.cs | 49 +- .../Extensions/MessageExtensions.cs | 18 + src/Discord.Net.Commands/Info/CommandInfo.cs | 63 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 39 + .../Info/ParameterInfo.cs | 35 +- .../Map/CommandMapNode.cs | 1 + src/Discord.Net.Commands/ModuleBase.cs | 39 +- .../MultiMatchHandling.cs | 7 +- .../Readers/ChannelTypeReader.cs | 14 +- .../Readers/EnumTypeReader.cs | 7 +- .../Readers/MessageTypeReader.cs | 9 +- .../Readers/NullableTypeReader.cs | 5 +- .../Readers/PrimitiveTypeReader.cs | 8 +- .../Readers/RoleTypeReader.cs | 9 +- .../Readers/TimeSpanTypeReader.cs | 1 + .../Readers/TypeReader.cs | 14 +- .../Readers/UserTypeReader.cs | 16 +- .../Results/ExecuteResult.cs | 54 +- src/Discord.Net.Commands/Results/IResult.cs | 24 +- .../Results/ParseResult.cs | 8 +- .../Results/PreconditionGroupResult.cs | 2 +- .../Results/PreconditionResult.cs | 27 + .../Results/RuntimeResult.cs | 12 +- .../Results/SearchResult.cs | 3 + .../Results/TypeReaderResult.cs | 5 + src/Discord.Net.Commands/RunMode.cs | 16 +- .../Utilities/QuotationAliasUtils.cs | 11 +- .../Utilities/ReflectionUtils.cs | 11 +- src/Discord.Net.Core/Audio/AudioOutStream.cs | 17 +- src/Discord.Net.Core/Audio/AudioStream.cs | 34 +- src/Discord.Net.Core/Audio/IAudioClient.cs | 2 +- src/Discord.Net.Core/CDN.cs | 85 ++ .../Commands/ICommandContext.cs | 20 +- src/Discord.Net.Core/ConnectionState.cs | 7 +- src/Discord.Net.Core/DiscordConfig.cs | 118 +- .../Entities/Activities/ActivityType.cs | 17 +- .../Entities/Activities/Game.cs | 11 + .../Entities/Activities/GameAsset.cs | 27 +- .../Entities/Activities/GameParty.cs | 15 + .../Entities/Activities/GameSecrets.cs | 16 +- .../Entities/Activities/GameTimestamps.cs | 13 +- .../Entities/Activities/IActivity.cs | 17 +- .../Entities/Activities/RichGame.cs | 32 +- .../Entities/Activities/SpotifyGame.cs | 63 + .../Entities/Activities/StreamingGame.cs | 18 +- .../Entities/AuditLogs/ActionType.cs | 86 +- .../Entities/AuditLogs/IAuditLogData.cs | 8 +- .../Entities/AuditLogs/IAuditLogEntry.cs | 22 +- src/Discord.Net.Core/Entities/CacheMode.cs | 11 +- .../Entities/Channels/ChannelType.cs | 8 +- .../Entities/Channels/Direction.cs | 14 +- .../Channels/GuildChannelProperties.cs | 27 +- .../Entities/Channels/IAudioChannel.cs | 22 +- .../Entities/Channels/ICategoryChannel.cs | 9 +- .../Entities/Channels/IChannel.cs | 38 +- .../Entities/Channels/IDMChannel.cs | 22 +- .../Entities/Channels/IGroupChannel.cs | 15 +- .../Entities/Channels/IGuildChannel.cs | 163 ++- .../Entities/Channels/IMessageChannel.cs | 262 ++++- .../Entities/Channels/INestedChannel.cs | 21 +- .../Entities/Channels/IPrivateChannel.cs | 11 +- .../Entities/Channels/ITextChannel.cs | 104 +- .../Entities/Channels/IVoiceChannel.cs | 29 +- .../Channels/ReorderChannelProperties.cs | 22 +- .../Channels/TextChannelProperties.cs | 22 +- .../Channels/VoiceChannelProperties.cs | 10 +- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 24 +- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 49 +- .../Entities/Emotes/EmoteProperties.cs | 12 +- .../Entities/Emotes/GuildEmote.cs | 28 +- .../Entities/Emotes/IEmote.cs | 9 +- .../Guilds/DefaultMessageNotifications.cs | 13 +- .../Entities/Guilds/GuildEmbedProperties.cs | 10 +- .../Guilds/GuildIntegrationProperties.cs | 14 +- .../Entities/Guilds/GuildProperties.cs | 47 +- src/Discord.Net.Core/Entities/Guilds/IBan.cs | 17 +- .../Entities/Guilds/IGuild.cs | 629 +++++++++- .../Entities/Guilds/IGuildIntegration.cs | 54 +- .../Entities/Guilds/IUserGuild.cs | 18 +- .../Entities/Guilds/IVoiceRegion.cs | 45 +- .../Entities/Guilds/IntegrationAccount.cs | 6 +- .../Entities/Guilds/MfaLevel.cs | 13 +- .../Entities/Guilds/PermissionTarget.cs | 11 +- .../Entities/Guilds/VerificationLevel.cs | 25 +- src/Discord.Net.Core/Entities/IApplication.cs | 20 +- src/Discord.Net.Core/Entities/IDeletable.cs | 10 +- src/Discord.Net.Core/Entities/IEntity.cs | 4 +- src/Discord.Net.Core/Entities/IMentionable.cs | 12 +- .../Entities/ISnowflakeEntity.cs | 7 + src/Discord.Net.Core/Entities/IUpdateable.cs | 9 +- src/Discord.Net.Core/Entities/Image.cs | 37 +- src/Discord.Net.Core/Entities/ImageFormat.cs | 20 +- .../Entities/Invites/IInvite.cs | 75 +- .../Entities/Invites/IInviteMetadata.cs | 55 +- .../Entities/Messages/Embed.cs | 22 + .../Entities/Messages/EmbedAuthor.cs | 22 +- .../Entities/Messages/EmbedBuilder.cs | 396 ++++++- .../Entities/Messages/EmbedField.cs | 20 +- .../Entities/Messages/EmbedFooter.cs | 32 +- .../Entities/Messages/EmbedImage.cs | 34 +- .../Entities/Messages/EmbedProvider.cs | 20 +- .../Entities/Messages/EmbedThumbnail.cs | 34 +- .../Entities/Messages/EmbedType.cs | 30 + .../Entities/Messages/EmbedVideo.cs | 30 +- .../Entities/Messages/IAttachment.cs | 47 +- .../Entities/Messages/IEmbed.cs | 84 +- .../Entities/Messages/IMessage.cs | 95 +- .../Entities/Messages/IReaction.cs | 8 +- .../Entities/Messages/ISystemMessage.cs | 5 +- .../Entities/Messages/IUserMessage.cs | 104 +- .../Entities/Messages/MessageProperties.cs | 27 +- .../Entities/Messages/MessageSource.cs | 15 + .../Entities/Messages/MessageType.cs | 26 +- .../Entities/Messages/ReactionMetadata.cs | 19 +- .../Entities/Messages/TagHandling.cs | 42 +- .../Entities/Messages/TagType.cs | 9 +- .../Entities/Permissions/ChannelPermission.cs | 99 +- .../Permissions/ChannelPermissions.cs | 70 +- .../Entities/Permissions/GuildPermission.cs | 135 ++- .../Entities/Permissions/GuildPermissions.cs | 77 +- .../Entities/Permissions/Overwrite.cs | 21 +- .../Permissions/OverwritePermissions.cs | 45 +- .../Entities/Permissions/PermValue.cs | 6 +- src/Discord.Net.Core/Entities/Roles/Color.cs | 137 ++- src/Discord.Net.Core/Entities/Roles/IRole.cs | 80 +- .../Entities/Roles/ReorderRoleProperties.cs | 24 +- .../Entities/Roles/RoleProperties.cs | 37 +- .../Entities/Users/GuildUserProperties.cs | 59 +- .../Entities/Users/IConnection.cs | 15 +- .../Entities/Users/IGroupUser.cs | 5 +- .../Entities/Users/IGuildUser.cs | 115 +- .../Entities/Users/IPresence.cs | 15 +- .../Entities/Users/ISelfUser.cs | 26 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 54 +- .../Entities/Users/IVoiceState.cs | 33 +- .../Entities/Users/IWebhookUser.cs | 4 +- .../Entities/Users/SelfUserProperties.cs | 18 +- .../Entities/Users/UserStatus.cs | 23 +- .../Entities/Webhooks/IWebhook.cs | 43 +- .../Entities/Webhooks/WebhookProperties.cs | 27 +- .../Extensions/AsyncEnumerableExtensions.cs | 10 +- .../Extensions/DiscordClientExtensions.cs | 9 +- .../Extensions/EmbedBuilderExtensions.cs | 11 + .../Extensions/MessageExtensions.cs | 10 + .../Extensions/UserExtensions.cs | 119 +- src/Discord.Net.Core/Format.cs | 1 + src/Discord.Net.Core/IDiscordClient.cs | 238 +++- src/Discord.Net.Core/Logging/LogManager.cs | 10 +- src/Discord.Net.Core/Logging/LogMessage.cs | 41 +- src/Discord.Net.Core/Logging/LogSeverity.cs | 24 +- src/Discord.Net.Core/LoginState.cs | 7 +- src/Discord.Net.Core/Net/HttpException.cs | 32 + src/Discord.Net.Core/Net/IRequest.cs | 3 + .../Net/RateLimitedException.cs | 10 + src/Discord.Net.Core/Net/Rest/IRestClient.cs | 21 + .../Net/WebSocketClosedException.cs | 20 +- src/Discord.Net.Core/RequestOptions.cs | 40 +- src/Discord.Net.Core/RetryMode.cs | 4 +- src/Discord.Net.Core/TokenType.cs | 10 + src/Discord.Net.Core/Utils/Cacheable.cs | 37 +- src/Discord.Net.Core/Utils/Comparers.cs | 18 + .../Utils/ConcurrentHashSet.cs | 8 +- src/Discord.Net.Core/Utils/DateTimeUtils.cs | 3 +- src/Discord.Net.Core/Utils/MentionUtils.cs | 56 +- src/Discord.Net.Core/Utils/Optional.cs | 3 +- src/Discord.Net.Core/Utils/Permissions.cs | 2 +- src/Discord.Net.Core/Utils/Preconditions.cs | 2 + src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 17 + src/Discord.Net.Core/Utils/TokenUtils.cs | 11 +- .../WS4NetClient.cs | 4 +- .../API/Common/EmbedAuthor.cs | 1 - .../API/Common/EmbedFooter.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedImage.cs | 3 +- .../API/Common/EmbedProvider.cs | 3 +- .../API/Common/EmbedThumbnail.cs | 3 +- src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 3 +- .../API/Rest/UploadFileParams.cs | 1 - src/Discord.Net.Rest/BaseDiscordClient.cs | 35 +- src/Discord.Net.Rest/ClientHelper.cs | 4 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 47 +- src/Discord.Net.Rest/DiscordRestClient.cs | 54 +- src/Discord.Net.Rest/DiscordRestConfig.cs | 5 +- .../AuditLogs/DataTypes/BanAuditLogData.cs | 11 +- .../DataTypes/ChannelCreateAuditLogData.cs | 29 +- .../DataTypes/ChannelDeleteAuditLogData.cs | 30 +- .../AuditLogs/DataTypes/ChannelInfo.cs | 29 + .../DataTypes/ChannelUpdateAuditLogData.cs | 23 +- .../DataTypes/EmoteCreateAuditLogData.cs | 19 +- .../DataTypes/EmoteDeleteAuditLogData.cs | 17 +- .../DataTypes/EmoteUpdateAuditLogData.cs | 23 +- .../Entities/AuditLogs/DataTypes/GuildInfo.cs | 55 + .../DataTypes/GuildUpdateAuditLogData.cs | 17 +- .../DataTypes/InviteCreateAuditLogData.cs | 49 +- .../DataTypes/InviteDeleteAuditLogData.cs | 49 +- .../AuditLogs/DataTypes/InviteInfo.cs | 37 + .../DataTypes/InviteUpdateAuditLogData.cs | 15 + .../AuditLogs/DataTypes/KickAuditLogData.cs | 9 + .../DataTypes/MemberRoleAuditLogData.cs | 17 +- .../AuditLogs/DataTypes/MemberRoleEditInfo.cs | 21 + .../DataTypes/MemberUpdateAuditLogData.cs | 12 +- .../DataTypes/MessageDeleteAuditLogData.cs | 16 + .../DataTypes/OverwriteCreateAuditLogData.cs | 9 + .../DataTypes/OverwriteDeleteAuditLogData.cs | 15 +- .../DataTypes/OverwriteUpdateAuditLogData.cs | 30 +- .../AuditLogs/DataTypes/PruneAuditLogData.cs | 18 + .../DataTypes/RoleCreateAuditLogData.cs | 15 + .../DataTypes/RoleDeleteAuditLogData.cs | 15 + .../AuditLogs/DataTypes/RoleEditInfo.cs | 38 + .../DataTypes/RoleUpdateAuditLogData.cs | 21 + .../AuditLogs/DataTypes/UnbanAuditLogData.cs | 9 + .../DataTypes/WebhookCreateAuditLogData.cs | 35 +- .../DataTypes/WebhookDeleteAuditLogData.cs | 38 +- .../AuditLogs/DataTypes/WebhookInfo.cs | 22 + .../DataTypes/WebhookUpdateAuditLogData.cs | 28 +- .../Entities/AuditLogs/RestAuditLogEntry.cs | 3 + .../Entities/Channels/ChannelHelper.cs | 41 +- .../Entities/Channels/IRestMessageChannel.cs | 186 ++- .../Entities/Channels/IRestPrivateChannel.cs | 8 +- .../Entities/Channels/RestCategoryChannel.cs | 13 +- .../Entities/Channels/RestChannel.cs | 10 + .../Entities/Channels/RestDMChannel.cs | 94 +- .../Entities/Channels/RestGroupChannel.cs | 50 +- .../Entities/Channels/RestGuildChannel.cs | 113 +- .../Entities/Channels/RestTextChannel.cs | 133 ++- .../Entities/Channels/RestVoiceChannel.cs | 20 + .../Channels/RpcVirtualMessageChannel.cs | 27 +- .../Entities/Guilds/GuildHelper.cs | 24 +- .../Entities/Guilds/RestBan.cs | 19 +- .../Entities/Guilds/RestGuild.cs | 387 ++++++- .../Entities/Guilds/RestGuildEmbed.cs | 4 +- .../Entities/Guilds/RestGuildIntegration.cs | 14 +- .../Entities/Guilds/RestUserGuild.cs | 8 +- .../Entities/Guilds/RestVoiceRegion.cs | 3 + .../Entities/Invites/RestInvite.cs | 22 +- .../Entities/Invites/RestInviteMetadata.cs | 11 + .../Entities/Messages/Attachment.cs | 18 +- .../Entities/Messages/MessageHelper.cs | 6 +- .../Entities/Messages/RestMessage.cs | 40 +- .../Entities/Messages/RestReaction.cs | 12 +- .../Entities/Messages/RestSystemMessage.cs | 6 +- .../Entities/Messages/RestUserMessage.cs | 26 +- .../Entities/RestApplication.cs | 21 +- .../Entities/Roles/RestRole.cs | 27 +- .../Entities/Users/RestConnection.cs | 13 +- .../Entities/Users/RestGroupUser.cs | 14 +- .../Entities/Users/RestGuildUser.cs | 23 + .../Entities/Users/RestSelfUser.cs | 13 +- .../Entities/Users/RestUser.cs | 32 +- .../Entities/Users/RestWebhookUser.cs | 54 +- .../Entities/Users/UserHelper.cs | 4 +- .../Entities/Webhooks/RestWebhook.cs | 15 +- .../Net/Converters/ImageConverter.cs | 3 +- .../Converters/PermissionTargetConverter.cs | 8 +- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 4 +- .../Net/DefaultRestClientProvider.cs | 1 + .../Net/Queue/RequestQueue.cs | 2 +- .../Net/Queue/RequestQueueBucket.cs | 2 +- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 16 +- .../Audio/AudioClient.cs | 1 - .../Audio/Streams/OpusDecodeStream.cs | 9 +- .../Audio/Streams/OpusEncodeStream.cs | 2 +- .../Audio/Streams/RTPReadStream.cs | 5 +- .../Audio/Streams/SodiumDecryptStream.cs | 4 +- .../Audio/Streams/SodiumEncryptStream.cs | 14 +- .../BaseSocketClient.Events.cs | 88 ++ src/Discord.Net.WebSocket/BaseSocketClient.cs | 228 +++- src/Discord.Net.WebSocket/ClientState.cs | 2 +- .../Commands/ShardedCommandContext.cs | 6 +- .../Commands/SocketCommandContext.cs | 33 +- .../DiscordShardedClient.cs | 40 +- .../DiscordSocketApiClient.cs | 10 +- .../DiscordSocketClient.Events.cs | 9 +- .../DiscordSocketClient.cs | 114 +- .../DiscordSocketConfig.cs | 55 +- .../Entities/Channels/ISocketAudioChannel.cs | 5 +- .../Channels/ISocketMessageChannel.cs | 111 +- .../Channels/ISocketPrivateChannel.cs | 5 +- .../Channels/SocketCategoryChannel.cs | 24 +- .../Entities/Channels/SocketChannel.cs | 23 +- .../Entities/Channels/SocketChannelHelper.cs | 9 +- .../Entities/Channels/SocketDMChannel.cs | 174 ++- .../Entities/Channels/SocketGroupChannel.cs | 52 +- .../Entities/Channels/SocketGuildChannel.cs | 126 +- .../Entities/Channels/SocketTextChannel.cs | 196 +++- .../Entities/Channels/SocketVoiceChannel.cs | 21 + .../Entities/Guilds/SocketGuild.cs | 368 +++++- .../Entities/Messages/MessageCache.cs | 4 +- .../Entities/Messages/SocketMessage.cs | 72 +- .../Entities/Messages/SocketReaction.cs | 40 +- .../Entities/Messages/SocketSystemMessage.cs | 6 +- .../Entities/Messages/SocketUserMessage.cs | 27 +- .../Entities/Roles/SocketRole.cs | 36 +- .../Entities/SocketEntity.cs | 3 +- .../Entities/Users/SocketGlobalUser.cs | 5 +- .../Entities/Users/SocketGroupUser.cs | 23 +- .../Entities/Users/SocketGuildUser.cs | 57 +- .../Entities/Users/SocketPresence.cs | 14 +- .../Entities/Users/SocketSelfUser.cs | 18 +- .../Entities/Users/SocketUnknownUser.cs | 21 +- .../Entities/Users/SocketUser.cs | 26 +- .../Entities/Users/SocketVoiceState.cs | 27 +- .../Entities/Users/SocketWebhookUser.cs | 70 +- .../Entities/Voice/SocketVoiceServer.cs | 27 +- .../Net/DefaultWebSocketClientProvider.cs | 1 + .../DiscordWebhookClient.cs | 20 +- .../Entities/Webhooks/RestInternalWebhook.cs | 2 +- .../WebhookClientHelper.cs | 5 +- .../AnalyzerTests/GuildAccessTests.cs | 6 +- .../Helpers/DiagnosticVerifier.Helper.cs | 3 +- .../Verifiers/DiagnosticVerifier.cs | 5 +- test/Discord.Net.Tests/Net/HttpMixin.cs | 3 +- test/Discord.Net.Tests/Tests.Channels.cs | 13 +- .../Tests.GuildPermissions.cs | 4 +- test/Discord.Net.Tests/Tests.Migrations.cs | 2 +- test/Discord.Net.Tests/Tests.Permissions.cs | 1 - 498 files changed, 16061 insertions(+), 2630 deletions(-) create mode 100644 Discord.Net.code-workspace create mode 100644 Discord.Net.sln.DotSettings create mode 100644 docs/Discord.Net.Docs.code-workspace create mode 100644 docs/_overwrites/Commands/CommandException.Overwrite.md create mode 100644 docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md create mode 100644 docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md create mode 100644 docs/_overwrites/Commands/ICommandContext.Inclusion.md create mode 100644 docs/_overwrites/Commands/ICommandContext.Overwrite.md create mode 100644 docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md create mode 100644 docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md create mode 100644 docs/_overwrites/Common/EmbedBuilder.Overwrites.md create mode 100644 docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md create mode 100644 docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md create mode 100644 docs/_overwrites/Common/IEmote.Inclusion.md create mode 100644 docs/_overwrites/Common/IEmote.Overwrites.md create mode 100644 docs/_overwrites/Common/ObjectProperties.Overwrites.md create mode 100644 docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md create mode 100644 docs/_overwrites/Common/images/embed-example.png create mode 100644 docs/_overwrites/Common/images/react-example.png create mode 100644 docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll create mode 100644 docs/_template/description-generator/plugins/LICENSE create mode 100644 docs/_template/last-modified/plugins/LICENSE create mode 100644 docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll create mode 100644 docs/_template/light-dark-theme/partials/affix.tmpl.partial create mode 100644 docs/_template/light-dark-theme/partials/head.tmpl.partial create mode 100644 docs/_template/light-dark-theme/partials/scripts.tmpl.partial create mode 100644 docs/_template/light-dark-theme/styles/cornerify.js create mode 100644 docs/_template/light-dark-theme/styles/dark.css create mode 100644 docs/_template/light-dark-theme/styles/docfx.vendor.minify.css create mode 100644 docs/_template/light-dark-theme/styles/gray.css create mode 100644 docs/_template/light-dark-theme/styles/light.css create mode 100644 docs/_template/light-dark-theme/styles/master.css create mode 100644 docs/_template/light-dark-theme/styles/plugin-featherlight.js create mode 100644 docs/_template/light-dark-theme/styles/styleswitcher.js create mode 100644 docs/_template/light-dark-theme/styles/theme-switcher.css create mode 100644 docs/_template/light-dark-theme/styles/tomorrow.css create mode 100644 docs/_template/light-dark-theme/styles/vs2015.css create mode 100644 docs/faq/basics/basic-operations.md create mode 100644 docs/faq/basics/client-basics.md create mode 100644 docs/faq/basics/getting-started.md create mode 100644 docs/faq/basics/images/dev-mode.png create mode 100644 docs/faq/basics/images/mention-escape.png create mode 100644 docs/faq/basics/images/snowflake.png create mode 100644 docs/faq/basics/samples/cast.cs create mode 100644 docs/faq/basics/samples/emoji-others.cs create mode 100644 docs/faq/basics/samples/emoji-self.cs create mode 100644 docs/faq/commands/dependency-injection.md create mode 100644 docs/faq/commands/general.md create mode 100644 docs/faq/commands/samples/DI.cs create mode 100644 docs/faq/commands/samples/Remainder.cs create mode 100644 docs/faq/commands/samples/missing-dep.cs create mode 100644 docs/faq/commands/samples/runmode-cmdattrib.cs create mode 100644 docs/faq/commands/samples/runmode-cmdconfig.cs create mode 100644 docs/faq/misc/glossary.md create mode 100644 docs/faq/misc/legacy.md create mode 100644 docs/faq/toc.yml delete mode 100644 docs/guides/commands/commands.md create mode 100644 docs/guides/commands/dependency-injection.md create mode 100644 docs/guides/commands/intro.md create mode 100644 docs/guides/commands/post-execution.md create mode 100644 docs/guides/commands/preconditions.md delete mode 100644 docs/guides/commands/samples/command_handler.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_module.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs delete mode 100644 docs/guides/commands/samples/dependency_map_setup.cs delete mode 100644 docs/guides/commands/samples/dependency_module.cs delete mode 100644 docs/guides/commands/samples/empty-module.cs create mode 100644 docs/guides/commands/samples/intro/command_handler.cs create mode 100644 docs/guides/commands/samples/intro/empty-module.cs rename docs/guides/commands/samples/{ => intro}/groups.cs (52%) rename docs/guides/commands/samples/{ => intro}/module.cs (58%) create mode 100644 docs/guides/commands/samples/post-execution/command_exception_log.cs create mode 100644 docs/guides/commands/samples/post-execution/command_executed_adv_demo.cs create mode 100644 docs/guides/commands/samples/post-execution/command_executed_demo.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_base.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_extended.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_usage.cs create mode 100644 docs/guides/commands/samples/post-execution/post-execution_basic.cs create mode 100644 docs/guides/commands/samples/preconditions/group_precondition.cs create mode 100644 docs/guides/commands/samples/preconditions/precondition_usage.cs rename docs/guides/commands/samples/{ => preconditions}/require_owner.cs (66%) create mode 100644 docs/guides/commands/samples/typereaders/typereader-register.cs rename docs/guides/commands/samples/{ => typereaders}/typereader.cs (74%) create mode 100644 docs/guides/commands/typereaders.md create mode 100644 docs/guides/deployment/deployment.md create mode 100644 docs/guides/emoji/emoji.md create mode 100644 docs/guides/emoji/images/emojipedia.png create mode 100644 docs/guides/emoji/images/emote-format.png create mode 100644 docs/guides/emoji/images/fileformat-emoji-src.png create mode 100644 docs/guides/emoji/samples/emoji-sample.cs create mode 100644 docs/guides/emoji/samples/emote-sample.cs create mode 100644 docs/guides/emoji/samples/socket-emote-sample.cs create mode 100644 docs/guides/getting_started/first-bot.md create mode 100644 docs/guides/getting_started/images/appveyor-artifacts.png create mode 100644 docs/guides/getting_started/images/appveyor-nupkg.png create mode 100644 docs/guides/getting_started/images/intro-authorize.png create mode 100644 docs/guides/getting_started/images/intro-bot-settings.png delete mode 100644 docs/guides/getting_started/images/intro-client-id.png delete mode 100644 docs/guides/getting_started/images/intro-create-app.png delete mode 100644 docs/guides/getting_started/images/intro-create-bot.png create mode 100644 docs/guides/getting_started/images/intro-oauth-settings.png create mode 100644 docs/guides/getting_started/images/intro-public-bot.png create mode 100644 docs/guides/getting_started/images/intro-scopes-bot.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-note.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step1.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step2.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step4.png delete mode 100644 docs/guides/getting_started/intro.md create mode 100644 docs/guides/getting_started/nightlies.md create mode 100644 docs/guides/getting_started/samples/first-bot/async-context.cs create mode 100644 docs/guides/getting_started/samples/first-bot/client.cs create mode 100644 docs/guides/getting_started/samples/first-bot/complete.cs create mode 100644 docs/guides/getting_started/samples/first-bot/logging.cs rename docs/guides/getting_started/samples/{intro => first-bot}/message.cs (92%) rename docs/guides/getting_started/samples/{intro => first-bot}/structure.cs (100%) delete mode 100644 docs/guides/getting_started/samples/intro/async-context.cs delete mode 100644 docs/guides/getting_started/samples/intro/client.cs delete mode 100644 docs/guides/getting_started/samples/intro/complete.cs delete mode 100644 docs/guides/getting_started/samples/intro/logging.cs delete mode 100644 docs/guides/getting_started/samples/project.csproj create mode 100644 docs/guides/getting_started/samples/project.xml create mode 100644 docs/guides/introduction/intro.md delete mode 100644 docs/guides/migrating/migrating.md delete mode 100644 docs/guides/migrating/samples/event.cs delete mode 100644 docs/guides/migrating/samples/sync_event.cs create mode 100644 docs/langwordMapping.yml diff --git a/.gitignore b/.gitignore index 45e5e009d..954362408 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,6 @@ project.lock.json docs/api/\.manifest \.idea/ + +# Codealike UID +codealike.json \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8248291e8..752b40931 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,22 @@ We attempt to conform to the .NET Foundation's [Coding Style](https://github.com where possible. As a general rule, follow the coding style already set in the file you -are editing, or look at a similar file if you are adding a new one. \ No newline at end of file +are editing, or look at a similar file if you are adding a new one. + +### Documentation Style for Members + +When creating a new public member, the member must be annotated with sufficient documentation. This should include the +following, but not limited to: + +* `` summarizing the purpose of the method. +* `` or `` explaining the parameter. +* `` explaining the type of the returned member and what it is. +* `` if the method directly throws an exception. + +The length of the documentation should also follow the ruler as suggested by our +[Visual Studio Code workspace](Discord.Net.code-workspace). + +#### Recommended Reads + +* [Official Microsoft Documentation](https://docs.microsoft.com) +* [Sandcastle User Manual](https://ewsoftware.github.io/XMLCommentsGuide/html/4268757F-CE8D-4E6D-8502-4F7F2E22DDA3.htm) \ No newline at end of file diff --git a/Discord.Net.code-workspace b/Discord.Net.code-workspace new file mode 100644 index 000000000..709eb0e95 --- /dev/null +++ b/Discord.Net.code-workspace @@ -0,0 +1,23 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 120 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "docs/": true, + "**/obj": true, + "**/bin": true, + "samples/": true, + } + } +} \ No newline at end of file diff --git a/Discord.Net.sln.DotSettings b/Discord.Net.sln.DotSettings new file mode 100644 index 000000000..ca75a7f1b --- /dev/null +++ b/Discord.Net.sln.DotSettings @@ -0,0 +1,16 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 296b6d1cb..8641d377e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,27 +1,21 @@ # Contributing to Docs -I don't really have any strict conditions for writing documentation, -but just keep these few guidelines in mind: +## General Guidelines + +We do not have any strict conditions for writing documentation, +but keep these guidelines in mind: * Keep code samples in the `guides/samples` folder -* When referencing an object in the API, link to it's page in the -API documentation. +* When referencing an object in the API, link to its page in the + API documentation +* Documentation should be written in an FAQ/Wiki-style format * Documentation should be written in clear and proper English* -\* If anyone is interested in translating documentation into other -languages, please open an issue or contact me on +\* If anyone is interested in translating documentation into other +languages, please open an issue or contact me on Discord (`foxbot#0282`). -### Layout - -Documentation should be written in a FAQ/Wiki style format. - -Recommended reads: - -* http://docs.microsoft.com -* http://flask.pocoo.org/docs/0.12/ - -Style consistencies: +## Style Consistencies * Use a ruler set at 70 characters * Links should use long syntax @@ -29,18 +23,13 @@ Style consistencies: Example of long link syntax: -``` +```md Please consult the [API Documentation] for more information. [API Documentation]: xref:System.String ``` -### Compiling - -Documentation is compiled into a static site using [DocFx]. -We currently use the most recent build off the dev branch. +## Recommended Reads -After making changes, compile your changes into the static site with -`docfx`. You can also view your changes live with `docfx --serve`. - -[DocFx]: https://dotnet.github.io/docfx/ \ No newline at end of file +* http://docs.microsoft.com +* http://flask.pocoo.org/docs/0.12/ \ No newline at end of file diff --git a/docs/Discord.Net.Docs.code-workspace b/docs/Discord.Net.Docs.code-workspace new file mode 100644 index 000000000..d9f442869 --- /dev/null +++ b/docs/Discord.Net.Docs.code-workspace @@ -0,0 +1,21 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 70 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "obj/": true, + "_site/": true, + } + } +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index a672330d4..4a06dccab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,15 @@ # Instructions for Building Documentation -The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] +The documentation for the Discord.Net library uses [DocFX][docfx-main]. +Instructions for installing this tool can be found [here][docfx-installing]. 1. Navigate to the root of the repository. -2. (Optional) If you intend to target a specific version, ensure that you -have the correct version checked out. -3. Build the library. Run `dotnet build` in the root of this repository. - Ensure that the build passes without errors. -4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter -to preview the site locally. Some elements of the page may appear incorrect -when not hosted by a server. - - Remarks: According to the docfx website, this tool does work on Linux under mono. +2. Build the docs using `docfx docs/docfx.json`. Add the `--serve` + parameter to preview the site locally. Some elements of the page + may appear incorrectly when hosted offline. + +Please note that if you intend to target a specific version, ensure +that you have the correct version checked out. [docfx-main]: https://dotnet.github.io/docfx/ -[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html +[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html \ No newline at end of file diff --git a/docs/_overwrites/Commands/CommandException.Overwrite.md b/docs/_overwrites/Commands/CommandException.Overwrite.md new file mode 100644 index 000000000..166a011de --- /dev/null +++ b/docs/_overwrites/Commands/CommandException.Overwrite.md @@ -0,0 +1,31 @@ +--- +uid: Discord.Commands.CommandException +remarks: *content +--- + +This @System.Exception class is typically used when diagnosing +an error thrown during the execution of a command. You will find the +thrown exception passed into +[LogMessage.Exception](xref:Discord.LogMessage.Exception), which is +sent to your [CommandService.Log](xref:Discord.Commands.CommandService.Log) +event handler. + +--- +uid: Discord.Commands.CommandException +example: [*content] +--- + +You may use this information to handle runtime exceptions after +execution. Below is an example of how you may use this: + +```cs +public Task LogHandlerAsync(LogMessage logMessage) +{ + // Note that this casting method requires C#7 and up. + if (logMessage?.Exception is CommandException cmdEx) + { + Console.WriteLine($"{cmdEx.GetBaseException().GetType()} was thrown while executing {cmdEx.Command.Aliases.First()} in {cmdEx.Context.Channel} by {cmdEx.Context.User}."); + } + return Task.CompletedTask; +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md new file mode 100644 index 000000000..d47980df7 --- /dev/null +++ b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md @@ -0,0 +1,22 @@ +--- +uid: Discord.Commands.DontAutoLoadAttribute +remarks: *content +--- + +The attribute can be applied to a public class that inherits +@Discord.Commands.ModuleBase. By applying this attribute, +@Discord.Commands.CommandService.AddModulesAsync* will not discover and +add the marked module to the CommandService. + +--- +uid: Discord.Commands.DontAutoLoadAttribute +example: [*content] +--- + +```cs +[DontAutoLoad] +public class MyModule : ModuleBase +{ + // ... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md new file mode 100644 index 000000000..950d2990c --- /dev/null +++ b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.DontInjectAttribute +remarks: *content +--- + +The attribute can be applied to a public settable property inside a +@Discord.Commands.ModuleBase based class. By applying this attribute, +the marked property will not be automatically injected of the +dependency. See @Guides.Commands.DI to learn more. + +--- +uid: Discord.Commands.DontInjectAttribute +example: [*content] +--- + +```cs +public class MyModule : ModuleBase +{ + [DontInject] + public MyService MyService { get; set; } + + public MyModule() + { + MyService = new MyService(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Inclusion.md b/docs/_overwrites/Commands/ICommandContext.Inclusion.md new file mode 100644 index 000000000..4c1257b23 --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Inclusion.md @@ -0,0 +1,5 @@ +An example of how this class is used the command system can be seen +below: + +[!code[Sample module](../../guides/commands/samples/intro/empty-module.cs)] +[!code[Command handler](../../guides/commands/samples/intro/command_handler.cs)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Overwrite.md b/docs/_overwrites/Commands/ICommandContext.Overwrite.md new file mode 100644 index 000000000..d9e50b46d --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.ICommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.CommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.SocketCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.ShardCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md new file mode 100644 index 000000000..75b9f93a5 --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md @@ -0,0 +1,103 @@ +--- +uid: Discord.Commands.PreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on module-level or +method-level for a command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on parameter-level for a +command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.PreconditionAttribute +example: [*content] +--- + +The following example creates a precondition to see if the user has +sufficient role required to access the command. + +```cs +public class RequireRoleAttribute : PreconditionAttribute +{ + private readonly ulong _roleId; + + public RequireRoleAttribute(ulong roleId) + { + _roleId = roleId; + } + + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) + { + var guildUser = context.User as IGuildUser; + if (guildUser == null) + return PreconditionResult.FromError("This command cannot be executed outside of a guild."); + + var guild = guildUser.Guild; + if (guild.Roles.All(r => r.Id != _roleId)) + return PreconditionResult.FromError( + $"The guild does not have the role ({_roleId}) required to access this command."); + + return guildUser.RoleIds.Any(rId => rId == _roleId) + ? PreconditionResult.FromSuccess() + : PreconditionResult.FromError("You do not have the sufficient role required to access this command."); + } +} +``` + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +example: [*content] +--- + +The following example creates a precondition on a parameter-level to +see if the targeted user has a lower hierarchy than the user who +executed the command. + +```cs +public class RequireHierarchyAttribute : ParameterPreconditionAttribute +{ + public override async Task CheckPermissionsAsync(ICommandContext context, + ParameterInfo parameter, object value, IServiceProvider services) + { + // Hierarchy is only available under the socket variant of the user. + if (!(context.User is SocketGuildUser guildUser)) + return PreconditionResult.FromError("This command cannot be used outside of a guild."); + + SocketGuildUser targetUser; + switch (value) + { + case SocketGuildUser targetGuildUser: + targetUser = targetGuildUser; + break; + case ulong userId: + targetUser = await context.Guild.GetUserAsync(userId).ConfigureAwait(false) as SocketGuildUser; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (targetUser == null) + return PreconditionResult.FromError("Target user not found."); + + if (guildUser.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("You cannot target anyone else whose roles are higher than yours."); + + var currentUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false) as SocketGuildUser; + if (currentUser?.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("The bot's role is lower than the targeted user."); + + return PreconditionResult.FromSuccess(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md new file mode 100644 index 000000000..499cdb0ad --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md @@ -0,0 +1,6 @@ +A "precondidtion" in the command system is used to determine if a +condition is met before entering the command task. Using a +precondidtion may aid in keeping a well-organized command logic. + +The most common use case being whether a user has sufficient +permission to execute the command. \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md new file mode 100644 index 000000000..2dcb1e004 --- /dev/null +++ b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md @@ -0,0 +1,68 @@ +--- +uid: Discord.EmbedBuilder +seealso: + - linkId: Discord.EmbedFooterBuilder + - linkId: Discord.EmbedAuthorBuilder + - linkId: Discord.EmbedFieldBuilder +remarks: *content +--- + +This builder class is used to build an @Discord.Embed (rich embed) +object that will be ready to be sent via @Discord.IMessageChannel.SendMessageAsync* +after @Discord.EmbedBuilder.Build* is called. + +--- +uid: Discord.EmbedBuilder +example: [*content] +--- + +#### Basic Usage + +The example below builds an embed and sends it to the chat using the +command system. + +```cs +[Command("embed")] +public async Task SendRichEmbedAsync() +{ + var embed = new EmbedBuilder + { + // Embed property can be set within object initializer + Title = "Hello world!" + Description = "I am a description set by initializer." + }; + // Or with methods + embed.AddField("Field title", + "Field value. I also support [hyperlink markdown](https://example.com)!") + .WithAuthor(Context.Client.CurrentUser) + .WithFooter(footer => footer.Text = "I am a footer.") + .WithColor(Color.Blue) + .WithTitle("I overwrote \"Hello world!\"") + .WithDescription("I am a description.") + .WithUrl("https://example.com") + .WithCurrentTimestamp() + .Build(); + await ReplyAsync(embed: embed); +} +``` + +![Embed Example](images/embed-example.png) + +#### Usage with Local Images + +The example below sends an image and has the image embedded in the rich +embed. See @Discord.IMessageChannel.SendFileAsync* for more information +about uploading a file or image. + +```cs +[Command("embedimage")] +public async Task SendEmbedWithImageAsync() +{ + var fileName = "image.png"; + var embed = new EmbedBuilder() + { + ImageUrl = $"attachment://{fileName}" + }.Build(); + await Context.Channel.SendFileAsync(fileName, embed: embed); +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md new file mode 100644 index 000000000..eac0d9ca5 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md @@ -0,0 +1,25 @@ +The example will build a rich embed with an author field, a footer +field, and 2 normal fields using an @Discord.EmbedBuilder: + +```cs +var exampleAuthor = new EmbedAuthorBuilder() + .WithName("I am a bot") + .WithIconUrl("https://discordapp.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png"); +var exampleFooter = new EmbedFooterBuilder() + .WithText("I am a nice footer") + .WithIconUrl("https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png"); +var exampleField = new EmbedFieldBuilder() + .WithName("Title of Another Field") + .WithValue("I am an [example](https://example.com).") + .WithInline(true); +var otherField = new EmbedFieldBuilder() + .WithName("Title of a Field") + .WithValue("Notice how I'm inline with that other field next to me.") + .WithInline(true); +var embed = new EmbedBuilder() + .AddField(exampleField) + .AddField(otherField) + .WithAuthor(exampleAuthor) + .WithFooter(exampleFooter) + .Build(); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md new file mode 100644 index 000000000..c633c29b1 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md @@ -0,0 +1,20 @@ +--- +uid: Discord.EmbedAuthorBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFooterBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFieldBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Inclusion.md b/docs/_overwrites/Common/IEmote.Inclusion.md new file mode 100644 index 000000000..cf93c7eb5 --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Inclusion.md @@ -0,0 +1,26 @@ +The sample below sends a message and adds an @Discord.Emoji and a custom +@Discord.Emote to the message. + +```cs +public async Task SendAndReactAsync(ISocketMessageChannel channel) +{ + var message = await channel.SendMessageAsync("I am a message."); + + // Creates a Unicode-based emoji based on the Unicode string. + // This is effectively the same as new Emoji("💕"). + var heartEmoji = new Emoji("\U0001f495"); + // Reacts to the message with the Emoji. + await message.AddReactionAsync(heartEmoji); + + // Parses a custom emote based on the provided Discord emote format. + // Please note that this does not guarantee the existence of + // the emote. + var emote = Emote.Parse("<:thonkang:282745590985523200>"); + // Reacts to the message with the Emote. + await message.AddReactionAsync(emote); +} +``` + +#### Result + +![React Example](images/react-example.png) \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Overwrites.md b/docs/_overwrites/Common/IEmote.Overwrites.md new file mode 100644 index 000000000..034533d1d --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Overwrites.md @@ -0,0 +1,81 @@ +--- +uid: Discord.IEmote +seealso: + - linkId: Discord.Emote + - linkId: Discord.Emoji + - linkId: Discord.GuildEmote + - linkId: Discord.IUserMessage +remarks: *content +--- + +This interface is often used with reactions. It can represent an +unicode-based @Discord.Emoji, or a custom @Discord.Emote. + +--- +uid: Discord.Emote +seealso: + - linkId: Discord.IEmote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emote format is `<:emoteName:emoteId>`. This can be +> obtained by escaping with a `\` in front of the emote using the +> Discord chat client. + +This class represents a custom emoji. This type of emoji can be +created via the @Discord.Emote.Parse* or @Discord.Emote.TryParse* +method. + +--- +uid: Discord.Emoji +seealso: + - linkId: Discord.Emote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emoji format is Unicode-based. This means only +> something like `🙃` or `\U0001f643` would work, instead of +> `:upside_down:`. +> +> A Unicode-based emoji can be obtained by escaping with a `\` in +> front of the emote using the Discord chat client or by looking up on +> [Emojipedia](https://emojipedia.org). + +This class represents a standard Unicode-based emoji. This type of emoji +can be created by passing the Unicode into the constructor. + +--- +uid: Discord.IEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emoji +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.GuildEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/ObjectProperties.Overwrites.md b/docs/_overwrites/Common/ObjectProperties.Overwrites.md new file mode 100644 index 000000000..e9c365d39 --- /dev/null +++ b/docs/_overwrites/Common/ObjectProperties.Overwrites.md @@ -0,0 +1,174 @@ +--- +uid: Discord.GuildChannelProperties +example: [*content] +--- + +The following example uses @Discord.IGuildChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IGuildChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "new-name"; + x.Position = channel.Position - 1; +}); +``` + +--- +uid: Discord.TextChannelProperties +example: [*content] +--- + +The following example uses @Discord.ITextChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as ITextChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "cool-guys-only"; + x.Topic = "This channel is only for cool guys and adults!!!"; + x.Position = channel.Position - 1; + x.IsNsfw = true; +}); +``` + +--- +uid: Discord.VoiceChannelProperties +example: [*content] +--- + +The following example uses @Discord.IVoiceChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IVoiceChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.UserLimit = 5; +}); +``` + +--- +uid: Discord.EmoteProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyEmoteAsync* to +apply changes specified in the properties, + +```cs +await guild.ModifyEmoteAsync(x => +{ + x.Name = "blobo"; +}); +``` + +--- +uid: Discord.MessageProperties +example: [*content] +--- + +The following example uses @Discord.IUserMessage.ModifyAsync* to +apply changes specified in the properties, + +```cs +var message = await channel.SendMessageAsync("boo"); +await Task.Delay(TimeSpan.FromSeconds(1)); +await message.ModifyAsync(x => x.Content = "boi"); +``` + +--- +uid: Discord.GuildProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyAsync* to +apply changes specified in the properties, + +```cs +var guild = _client.GetGuild(id); +if (guild == null) return; + +await guild.ModifyAsync(x => +{ + x.Name = "VERY Fast Discord Running at Incredible Hihg Speed"; +}); +``` + +--- +uid: Discord.RoleProperties +example: [*content] +--- + +The following example uses @Discord.IRole.ModifyAsync* to +apply changes specified in the properties, + +```cs +var role = guild.GetRole(id); +if (role == null) return; + +await role.ModifyAsync(x => +{ + x.Name = "cool boi"; + x.Color = Color.Gold; + x.Hoist = true; + x.Mentionable = true; +}); +``` + +--- +uid: Discord.GuildUserProperties +example: [*content] +--- + +The following example uses @Discord.IGuildUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +var user = guild.GetUser(id); +if (user == null) return; + +await user.ModifyAsync(x => +{ + x.Nickname = "I need healing"; +}); +``` + +--- +uid: Discord.SelfUserProperties +example: [*content] +--- + +The following example uses @Discord.ISelfUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +await selfUser.ModifyAsync(x => +{ + x.Username = "Mercy"; +}); +``` + +--- +uid: Discord.WebhookProperties +example: [*content] +--- + +The following example uses @Discord.IWebhook.ModifyAsync* to +apply changes specified in the properties, + +```cs +await webhook.ModifyAsync(x => +{ + x.Name = "very fast fox"; + x.ChannelId = newChannelId; +}); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md new file mode 100644 index 000000000..29b547e49 --- /dev/null +++ b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md @@ -0,0 +1,24 @@ +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +remarks: *content +--- + +This attribute is used to override a command parameter's type reading +behaviour. This may be useful when you have multiple custom +@Discord.Commands.TypeReader and would like to specify one. + +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +examples: [*content] +--- + +The following example will override the @Discord.Commands.TypeReader +of @Discord.IUser to `MyUserTypeReader`. + +```cs +public async Task PrintUserAsync( + [OverrideTypeReader(typeof(MyUserTypeReader))] IUser user) +{ + //... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/images/embed-example.png b/docs/_overwrites/Common/images/embed-example.png new file mode 100644 index 0000000000000000000000000000000000000000..f23fb4d70d797dd65e40fae54e84cd1935dd8608 GIT binary patch literal 15290 zcmbWeV~{0J_%+zpG^cIbwr$(i^t5eE+qP}nx^26sJ#E|F@BRH__rpfKv9a%dsauiv z$*RiA%*u1lb236nK@tHD2Mz=T1VLI#Oa%l4Q~`Ki0s{p+vX-G`gMbi$NQ()pd1PPq zc&n*t_Flgy07P6;$kcJDnhP#UciG}Dpqy~WFv#utCD_~wZv%gCdfath4R8PEc;&yyGbIzReGE=WUvMHB%571_CTli0J>@&kld+R-^=>j%BBt>t~Sed^51( z%dn+7dO<&XeiLBUmUGi$ZZ7?3zreK=7akTKXWd5~oM~HUnW9x?@3dzzU!l4;JMJfC5b}$3%0BkrZm#8EB2(BD*qWC76MkI!!^V1BEsv>0a-3iNZG>>DwJ2MEP@0nx-`qNvp?+@e$5mg+9d$Gz!0|3U|)N6B#_2Z`N1u@DM*NR6U&3__K)FThLRK z)DN=CcXg95Bq1Xljl+QA+9zjKKa9!orzvu=>|Gy%o3Te^9<_2K?9?B~xI2DH$gnM2m`w7kqvVr=~ z2MccyMpMi!Puv|hYQ&<}%l$Wh-l2MJp>^NZWF2Z5OOoit3ghvJ^*qYI6Hm*A+v*?q#B5QPqHeg?##62p!emwpBEw={P;1RL(XU^{e`P-@mi`v z8Mo-U^6DP`XHmaMVuE%U%)MkF5^YNE`XwTFPH_B2nVb*5hnldJt;o$#JbyhVMe8pyUXq>idv>B3*8t&}*YLp_;!deSux# zYZX=P%T|;3M|t&_nc;?yd!98~4_k)RB#J?`w9zLS5~&;q9vSMh2acLL2nyM$i&51s zS^r;%{ra0*TY`P9FN`uB3KnuljvI}*C>;d_6Bzn1&bzN^-(9c)`2$VSN5xrvGTwu} z^v4tIt8V~d(pQ{mLNm|5caa2<-~^FcX1;%LhSX^hg#NkXNbJn#loSLX#^8Xy4=)(H z`o{5JpfRajATF4(e`xp6 zfdLCwmAIs%4o@UB?5q0e#FKFBj$M37F|&8;)64A}NcV8IF{b5(_&XB{5de;2f-(d4 zTSIC!1fnJsfBG`ye`mme-%ZFw9iU2ms&ivzw{&N51THmWzB)%GseXHd zRji7mvS`qeOYRI``BPu}OkjRw&MjTj#;ArM!m zsA52$6po$F>y0+D?K3$#**(hpZ-5OORl-h|&xW`=!5CSoukSlyGtJ-u8-?0#0XcTN#>KWbdc~ zTIuG%a=0odoq^E*poTy%X=pYtvnu#})uLZB9G{YdctirH2_|PDFTu9-i^9KzHi++H zN%gHRgU$aS?qw)5ep5G^YAb{b|p*T->r_fklqwLy1uZr=u{YeE6u; z-(tvaC6$3qb=fU9^t=5H8zV+aSKT$iOG`e0Po%-Qbyk)bevutU8@+Y(%6i9chf%75 zl&~AbDU><<|(aBv=MhCr8TiF0!3yXV~mv6K^Ld>^aSF<1f7DJ0&P#|w_1MV*{1jt2Oh z&}m1fExW(=MF;`-?1b&&s6rB8B#UXP$$ncn*)czfi1+eKoS0A&Frh2ppsK2=(O=S@HSN%HKHxiU{H z*^UjydT!rXYEh)0A`c_Bp_X37*F5{;BXI5naQ z9oz|*7tM9IA9v|I;T6X{@?w_t9=1P=wHd4ikKJMu7ncV@>L@- z7>{kF@3y+o6PAS^85!jR);yz|0@-d}vu1bR^h#)8IBeTjd8dD9Czekrz$Nv>sac4* ztSYY0vu#$?*jdQENyI%-qJLME)YttXjDieahADr0BsgW`%i9Z-q zqj#uxwT#7}#$vjz6}~Z0bg50~@34sl4k(FKj_UBr4g(~jvT100H#?mZ;iR^P-2B0o zr7fHF)y|X3%88lF1Q=X22s7Q=u^CB@eDY&crTx0n^l3bnlZs38(nEKiipW)G>&^^? z^g;26%Li1J^Fx3i%g8!xNb^!BMxob+hRReScNLd8xbwJ=#dOAO6gwOnf$>WA>(pBt zeLu||rNWW%tfn+6SEV+qBg<=@aF2Z8m>C>loZ~a{P~o5P=+Vemta@M8yjClZlMDv` zk$S636M_Y;mk0j1F{eEff>;-)N;CCqbl<>Tjy+A3<8%~aAH&UozK;vAgsa_?ffI$N zk2qN$PhyZ3e7{7GKR5NbmTcg^or-Ivv!`kM9Qz!3xKvw$4SSzbkvJnYRbMusvHb2? z;W+d1kwKs>pZXxxR3AnW;lBm4Fgf#f#h)7#{h=MQ7v3Q0@|rUz;81lJb4?!WRuVd& zJnuO3hzR?w_x@df1T5EqT<>TY`=?0w z;^6qsC*w1pIpid;p{2MV<_Aly>U4bq>_3Ht-QbDo0owPuid#P*L?@Kw5km!c&WjrjssAt zIGV4_l%_U!EcZufmlXf<(nmx6)<3|aAHwJAYaM>g;SxfS>O+C+o4GY+`QEp$h3ej4 z!l7lT&Vt}Kb;P8Ga`JDo1tQ0@@5#fCt|tj#r#>+y51zWqs9NC6e81}6^rLT;U)G=G z%s(evfh{#tmD`|(8Tu-RmkK##-Bxic1kBhDdEE9e(|Np*L^Yv_1l4sAwp<)-wAePg zBOcTp8ToaHMmZf4{9W+yMtK!sf(+sf+JUvi%u$Khnd_Oa&xpq5;SXK&p0w zLYxg`M7yLST>p_P$q)xPU{oB;UUwPn<$=3@fne6hyum)(|>24}dS!)CZ?lUy5=r^OL0pgZmesDBfj?QP=nR!T)UnW_Y zsLF=Z@XoizpeWv|cVmc1-l{)Mj+GgZPr-#)+)fwc^)7^xte=N-HDv6`%uZ_ySgL84 z+XlZJp+?)Yzg~6q`phvFG)vO&irDaBif+Q#y}^G*T=slv)sCVYMKGbdx^RMAGWYcp zJYgfLrFO;wFmGIUVF|d@=iB^2EEgvAxW6e#hf?Q$u1dUw*?RjIO|JVSF@tWVD?_yA zk_rW#Ye({I@Y5g$&O!u%w*MfRLF@JOVV-WxXdvvFTstDFUY|7~M1_0efWe*RWZtW- zRKa<|qHp#?T#&2wJZ)wt{P#|HMIonf90kZN#211)&Pr$Noyz(X(`0o~1K|p>$Re=C zpFMMA^1+3Zw6OKq)G%UOupDih{y{|Flv}9nouDk=`emm!gS@eUh@XbN?h?8i>!A7B zt*xg-i~YUV#g`vl_SHbHcjbC%2f?1w2~l+kF*KXkwO9W0o5j%FySH@Vtixg<59zJI z`Ge8wW!wWy(eB`#JOw^(taz`C)q5Hps%@a;R)PWaMl_j>)bc=eYc*kHgfOP8>GJ;T z6TCSNAJ73y`{G?0J9ot6buAP>tr<7H3q~>O*Bk?#xpAXnt@Phe1N+9DDVMAt;}}it zo^*IWhOg6`ey%R!dM~Qz4;!1-Y!H_R$!8rMdu_Eaqiv?-my!+3m@mr$l9$*0?YhPBJ^oaO*o!KaoL!%;?ACAK{g*=u`!iP59ELi#{sO`O-FYNoc$QB$R@Iw zwYjqWD)h%qQDK|L6mDU?dvKG;7Cf%CH z%sHepBcMa%>`bQ%E~DTjOpMeMsAmaCuAWFpB%neIBB`Q?%U{ngm@u4@T`>-{Mz zJJ5_W(7}ha$+PQ6$myMUZg3cg%MqzKAYjX^zbRyhI|sczH-`dKR9&(q{^T4@mP>0I zpfTG?J~pk&jhixJwcbyD4r{W}XdRs)q@~N1HnZAMsswd|B(#<>6n$ZeyB=GwRG(Ke zJEd~A{KY6ugk|z!$i+)U#r6ONdIk>AW5IvtRL`D)g8!UGf8u?@s|MrDFwqQ#i}M3uAi*-H z{$d&r2Vr;gE%AbHz(@_a{8jSDYb{1|7v|DDfP1mHAo69A`aKGL!(Fg4sTZCRY1^fW zFheN#-;kHceOCF5hSe`mIAT7=T8Gd&Z)aSRy2k>3Y1@x6p%3!DYKva+3QP(9ox{XT z<}P9T96BOgGAp|70H~4wLVXDCtF;vg^2D+x`3)Rb)D|XKBC__@dM^kT&qSl4&WlX*gIO!6-SJr&lHd$q3)t5=73T9WWFn81*DWNYbGUov}(ZAu}em84+XWyrGU;zrt zqewC<&wIf?{~bwMA@QI4r5-~#-K%Yg%3tx~V=Y;(G0JyE2sDNCeLtY%fUX;igKKO( zN-et*mgOYOeIJt{!=w16RoCP(oWLW5aWuslH-;)`Lr2yfZ^47FVew#WWO^?VLRUrv zfZC(Har`Yt0s|~C=#!OCGmE+Xy|6qnR?N>KQiq>}>}5?J-vGXrZUtOWyQ3_wS>XT{ zweJ^j=71Q^a0|`PO~<@F`M3O{jC|td@~ltzEf+Ra7uh}|h@gSJZ|DD6<1JzK(0~Fh zA<7^%5C8~}{3mR}_=v#(MM&hA@c(x>@IMuAardTl+LOgmuba_>iF}zyCUd*xb4Hl% zPer6rMDLoB)Rg}4A}TJ zL&(@8C%WdjLJo0Gn09xLj4A_fDd^yu=iv8GZB%$7|KXLWE+H;+mOG0i99-dI4X~1k ziBH}&{VJisr0#edb9Y~e*LBzvGDzoT=wCIQiFrxU!nDS$`1?7}G)i8rnYprlkqJ^g z7_h(q;nu5)b9E%MpZ6B5SzJ!E!}Q8^6xNiq%EK$F9zc_fd%zcMR>$Mc$g0o;xpsxBBBH8tjMiDq=R zZAx%tGoHC&7l3x;PbwryTjfjO73?U@Q$3F8$A^(g28v72$X4!VLP@DmkMDzeGuzr$ zJEGZ-?k?EU4As0*^R9Nr{7{7`qEf{i&dNp3K>Sc)ApF?WuCu6Ic%UC8IaRw&@VlmU zRLTxbK<2E%<)#it;JIG6X;$#+Q8O9?1}O_0@VBNi5zvjM$OU`ctDtf)!cMue3}{WJ zb0u7;RWk@KOSZE|{*}q5%dRMYY*VVWdXRa=JA3~AjiXju{77k&cHw5#q9j8ftZ zqy~!-o~M>EzU~`OC$j(;mryec#6f!?j;#&nI$!(F%6P`pJF*${NrV=xcvb z44>bSn#xBX%em5^3u;w3k}GY-rsQW@#SY!tUMOe=KLbT41BSytccM+3o2^BE1TKD2 z3s$dEsYpq=V5g9al5@ya71wcRll;4i{9p%lY#-*GU&AbU)CQ5$EhkB+xF>a=f4SDG z)`!yGdyPUnR;jyycHx}=v}%RWFKF3+fvN3 zu6Mi6{9OV*VN!Y`iMt!Wf2KnCMIM+&Kp#BCp;1(f6DjHpoxr*HT++mzdQnjrBlp+A zfzlqa(2O@*#hBwv43ID_D?z%ITChrh&p@l2x?phG&_Bg(CH(hqQPA|L(ZqtMCVf)S z_&ks&kxj?pfghsv(Ycxvo$I3Gf zj=pdS?1l`ImVKvjUJfY81t+?qbry|yPDb8rS3rwD>iCBYB5Y$g;H>o-qrWFG z7ay~*+H<_Nk|-cDlpPR7NRkHggj3-AK2$k%xdAX?dU;>I@yZND8g>5`QYWRCSt_|7 zKgdE56T8v5;D%-X3;z~@$}A1234Qs3-!XZfJ>O``eTr z@&~38UR4m8v2AmqONmPL#sDBp#DgH@=(G^zPyiQ)+# zG8zAZg@MA~7{$>IQkquu00qt#7s|W?Elc)xLhqTEH)WHf>&U%gSM9*8^cY>4uOh;? z&dw#xd!sY`R5nt|Y15`^W9qFk@I6{yj-t_=G7MT-lc*iDUA9qg0_$gCegon?^32fq z{4JJMVX0%j1oW9;&>94`q#bxFf#k!e2b&E3BR>+d@UI|@kb8zgi}z7si80_5)Y$*7I{S|!{{L7% zdQAth1AF^I?B8G*b-{NEdm+FE+%W`EX2#Jzjem5tdiQP@P~px2b41^ILyXXJ2TEhL zk^0r1opD3XD@tzzHo3Ub+~7~#LWaNx;f(>3Pz+xewu2r!a{)&Zq>)k7G{7<@JZ$0v<}L*Zsni0I^D)zUYwaD8AWP!ZVHP7d#%oLd1sah^w4Ue(i@h43>xd zh@9!#%MrS>_2WSkH*>K7iqMZsU9FZmT|;ZM54RsBfLMpSB>HM?4o{e46(FbX+tVC8 z{mHnBcg#DfN9{a+#*9@sXA7G9FK1_pLYjZ(EXDq|AyMcnQj_YYQ02|MPKk{Z)aI$t zYY0Vw7I~oy2Xl&o4vxz%W@&ZPvGn0&aYd|09JCDIxd%3jwL=s4egz{TuUzF#g1Jf- zE%6AW(M#JfhyfYq$_*&O&Raz+NwyozEBC}PW=)9V{K)%V)gLHw*a|ljf+d}^*<`{3 zi5jESlm;9ov0K+QCa-%Gb<~_sb@YQ5Aj=xUdy(qhJFNV}fv#wo=t`H_ddkDJS4-Vn zbmm?_`MMH6TdiNYB{=B_d0)k1rx_QxVEZ2!I%lVxkDajtP6O#cy$Jj&;={TovK6L! zMt7O5*ydDPu2R?5hp5PiSbVv~S2g!?xNxBpnlyp^mGY9}fQX3NMJX_HCcFecPX^9U zI;=Z%%AYjuf6(ee4r~q4Q{v+ryb0*T_>}G|gKg?#b$gQ653y3dSrSsC z?Agpbz;goU)}P|}=e~T>aWStNhwwLy@GPw;MZR&GS;EmsQo5myv3DgMO7`;0w7RxZ>by01mTQkhvInKsv7MO@&} zpK(Acg}#bNnJ^8OtWhO?}Upz(`Z4-ZLZW8f|SEgqPQnl^>eWl>b= ztZFO&fx415CTL~8`_%Tj9n2%Tl9$tFU6Xlz={45ujFylp_oeU*t-4L4U8gfvJy`r< zYd@p+WkSsRw&>)6y3E+I`Ng6LC zNAc-&D?8s5&l~OTNRnQVDSy%t*ltZ?vFEiNyOqm&r;?u65xH=~`{^;6lK^}}4Xtfj z{`u$K<9zSYsjBuD|J1w~Ua2KW9(BmjrqrR=JIi8o1MOD^OR%Qw8&1=^jP`vD<}~l0 zo_I8qU_1UD7aPw7)ieafwc!^Yl@=XiH6F>4EhGO?fLi`t=(33+QyG!bYPli~W7{>BI=-`GPYN+i`@tLRe1fICF32c=7g_a6`se ztmFCKb8xkDnY>Y$kx1}Ip~bn<4Ej1vcdOTnz}J`EtbOD15XqEbRzY?&KSk+M-lx-_ z^j+TJ^V&!eRoa=d$d)@=J}rV+EJ#|GTB`am2SL)ymjfhcgF8Dfeqy!2?&Rx^p_A2u z2k;G+k8S zw-K>A65p_niv~MZ++z0v2OPGCj!pCSL@E>PMK;^(_GY7N>BvDGD9{}aKWqm7dOtJ+ zK7yjoIGW{;^^V2)hd2Ytt!AU0U+?NLm02i^a8akm`mV?eXL{xpih^)X4~Kf?U#9UW z10(q#$R09J3!$*gIY z;6J{$QB>OwA*!tm&}8=)je77Cjw^kwk&o&$j-FQtdEQKRzZpVPMXR+NA|J_HT-Xcd z#as;Yssg0+#h$MtlE&j)l{$^_&t-pM-C^ASl1Z~Il$xFtQ*8=nw|zEAT^RGHSUp7I zP7C^DEp_zYRbqYVS&$fga`CW>)**PXH#?ii_gb!4MdOG=JV1+HxUg}HMX z{qd&C6H|r>n4CQq^cX-vYcxBs^2EJ0`KfU?J>o9{&}KL*nqF8&XHLMsXI>he^*&CW zbcR6uq|Qyhhsn<^{OTVC!<%D|rxY>U!^d6%XK z%*XV*3$MY^n~yXoc1TXGK!QXyvy^5X-GH(p%GR~Pb-xIyC(wsGw6eq6c_2J9^-9iB zk=;P}#XE1={NcMG=YHwfJV_z(d5Uh*R!h-+Dcn1izT5ld&>Jy%G?`aAP=SQKe6}u& zQxiwOi~F9pRfs)vctNfuA}IO2=g)L?0xQDHS1E};7taj#sz@8L)e@6ntor&MLI)DW zE5c2fbXc1{I6?+)IMQWDlr1YQQQMNC-y7`-R3^v>P8n@q2WkaT)7enFR^9^s9kNV1^G%%Jz!0E@*1<^$sv*viyRI1mj*QSKtOqr-eYb9 zJ_U`_U>zd|P810rEGIjS0%C*sYwt7BW-3xHya_alr0ACUy6!&)#69z7s?jfV+Ny&W z3eo6eI~sh@-Dk{LLP2XqrbDLzbDOBVD4LN1BwN?q`;i=rNz8M@d(SjXv~Wj>F&DPU z;WOstKFuh3 zdo!NjhU-=9&1E=0G-%rFix&ulRSZ@~q?(hvN6sz*71n%V9V@YL<*ht6Hd);gSEq>Q_-F?%`;{yP#E=s$lUYu4LyA;7C8et8h1r*ivM8Le6Mzhkz^R@l-FyA4p1C8EX#W@ruvenr>U zb-U}77D_j7@M^NClt|Y`{x#Rm@aBlTi>_Pa=W2=(s+kU4MZ(v2=&^8dMR4?pQp+yB ztQ(FVSt=)SvAu=Cj99L3G?x3S?R8UuqzqVFd6jQ`j4$0evEj=3_~PeSqzJDS1d@zD z3DzFFa^mlUw~^BNWBtx#Br*@|PTcLTp9x#saeZq>Y};|3XW7a^MbEh<$_reaTHhVA z@GB%$H34Od3pxsat>eN4NV0!K!ZLbV1rqKj*Zo{NTc6VoT7us7c*lb@Pg2;7iaGUhV(3fa^NH zL<1;LXrRhV9iYg7j+xE>WuwxTEbw0Z2c+;m1}AR$g)4*w4x-;#L`WDz*^qLaY>kgC zZF=`Xr%}{TUPv0?@0`^pf-IpAdKO316gwKjUVjXF9S=_*_Nf<%SNhyw{!y{q`Henw zG&R($oit5H;9xHa9LdZT3Xd2&zE0p)t`5)aX1!K0YG|ij`9oP6Rc4xxg-j+3Gtd>;%oc7~Zr|p)%_lx_%zmrtQ8sejh{R ztxvA?<7KjBGAHLapt}hvK$FYumzDC{j1;zLg$Tr zfgKBBt-KoEWhlMHBGiRy%XqEnuM0f`cWOq7!keaxd*%8Z)^qZj;Z4=8AD@@IuC$yY zXYb5&>4CPfp2x|+rk__?PC;i4ibv+fXtSDt?GSrn?%M|`Mt**-6NnKHy^3U4*H}6+ zd*;IrkTWr0zz!hxg<@Uo@wb2eV?bK8f%7+t8pUkb=GM(TG{g*^B_OEd1KEPt%B5d9 zX)+AOe)^Fa0G1p-+@V;GjWzWGRc~_dQg^$==8?5 zw@)L)NnDhmIo>Y2cj`Ecou#4=tR7rCB_%fTst~<}eq*1<04HKoyn!;LCwy@Tg?v{Z zDr1B^(InU$4uY`bo^+ITE_-E;ll~*l2p;yxQ9xRIXxmOuY`rqP;CIJ654#>d?lwR( zg%T6n&xGG}(87sgh<|Ir2P$V}tb~Cu#KO_>0_GVPn7P<^;Ss3ninv8>&(Fj@{rZh) z#e{;zo>B)s9a0(a$a?j|JEU%qFYFXNKaPT$9CV1N3W?oad$=Rb_i#3;uRffl3%^;1 ztR%vh|N6%2@Ay?_37w9XSTW>D!p$= zU5PmY-^cJhTAdmwq;m|qq^6ZlOK==&p5>dYc<}~0n-2EH@-H5cw_!7thQ=%>%#Y?Bqk-ktYjkbedyKUB_vdB#Tv$b7q zN&dOP1D^G^zFSrw4O>h>lm4vM{^j0KIuE}QEmrX??n^m2lcRuFinK=h5)*bU9 z4uqULDzIb&*)L9EuFC@CCAXRPz5a$~Sxi`)E_PM=UXsL?0;5{|;Tw7DGhfl3I)3&Q zgsamK(Dz1_Z+g3qj8?kRPJv;|c!7(ix`OUp`}Nk6g4wAe-+R$oiIMy1xS@{kVVk_! zP!6z-DNe`joYDVMDI#vM`CrAQMIGMyE|Q)I=pJ*A`=yb z!d9^d;E0|=md5fkck^RS+J<5g)wOnq92R!D=d&c}^+v)C>8`^*Mp+bl)c zao|+j=utak9rqjzA2t0})ScG-MRRMRhNF!QdophV^cfD7l4*Mi0zINieg0brUr9~Q z8EJ)*?1W~IZnqxuJ&{1IP%vK@PT87zXL2tK%|3>%=oQ1e>v_B$WR*i>m8awA(qkM$ zqwA0H#^U6E$qJyRZ_j>>$Aggg?+{m-v|z=5uwt!Z7}N-|D`E>eR;dnTp4G#+HX%Ah zG@Tz}O79-F3u&q2Yz%0`q=+ewP^yr!T9T02S@RrXc;1e0GM(eSToZX1HlIvRdDzyO zjn9N?(f5v!h)1uHR9A0D&uiMP9q9X`nm0PtZ3d6C+CMq3GTnO#Xds)~U577kJCTZU zEs5oFZHLvDU`fDnNRMlBRcrwu(*{KY$`Wnj_5H69f22UWZO3D?SQ=A6ji)f*YJVcG z5GqMcWlbk*VJkH(6Pm7qYKF=V4Fg5;w8EAa?BfSXT6>&3W z2Cb76cogm&01pd+oYP%*+`izw0W7-}OZa6Nx>^ z96`3KhuCH#XE4I9cFE@_o7bV-vq{AS&XP+kix>F!`2YwqH?@?Vj6$Ol`oaLU2|sg=LK2)iQ~K|DP1vL zrIZ#)T)L7!!l$7J=NTanY8{==F&+3@ zNXqf=Xz>#E%J0M5ajbB^UaiS~Q-TBkH-u;-EwgTTh=e7X^6D7ioz}|nOnRERa0SoI zFa|;X7a!8rTs*pZgHne-^!3NJA``0o*)VR>fI30VXkn!m4U&pZ36WyQ z&=lrqh_K%Ko0(2uH~M++S=9}irzb%SggZ&9C#a?p;8Q*%^SOtl=eur}$l_FR$Mrtt zR?psO|Lv%Nu;G@m#7D{_Nm|w|&uKPWX0)yX=a&n@o>yOmH)k8~B7xDZVg!y|kp|a8fkhNsN*gxu_Piei|tO`g5^pel3%$f@X_%_s5m=hb~ve zky*-_40FO>tVySYlntr*HGaXUTA(OoOG)O|A;Qw8-cWh2|4NGaa(O751%E>9RcNT* z8V-7PfU0%=cHvFDFcSUp1t;~I4U+OYn3pI^LxUF^2kQI8B~|LdMO9nTJZ?lY+pYt< zP|tXz30vW6KyjYb*WU;+8KQBNRaI=>GeM)zz(`OGtlGU9-t*EVTLZw^rx7r4joUXL zEX$Psndr()Ow)+cN;7#1E`~d z4Dv$d_#C}pbw3^Ht~}KVQFGyXMPJ$+O8Lf}f3N}>@pw_`xo3|rWrpW09T6?glsVw< z{)S*PXKj; z4&Ebx3g2q=Lm(kyI%^sYJp1cF;)(M~d3syS_tNpHuPtK{kv=pTt3S5Gmi(_l*m`N< z{G*&q%BUz5_A-h{v4LW-0W>1Iye1n$JBdO$qy2Dc9EGh33lz?4GoAyNFeysJuE9v% zAJ*k^Xj#bfXFNc3v`Z6ggNjM`@ZdN?Z3n+6GQ#pE8ku}DFSZ&QR7}XUihRjPXytq& zIrx~_shOIt>b3rURrOjtc1@eAy;F&6_|kdLwnh>cb6PeUW5;};+eqZ$FH%De_JswL zXMSVN6!a__Rg{emKuyGip;QuK!^_BtcpV}i>TCgHGBoxnsaB3C1EmdRG+ll z^N|7>RZ{Hr3^Y1f@Uox-i*KDO9Hufw3fpxR0*Mjk@X#~aSY^RyaWuM!LYGSenX1C{jE3|eHFPouol0)s>NXb~ z6Vz~LEp9zm+ADm11#5G(MV4Im^}xuQyL{V*115P*3Y1LX#zZ4`0=?|OGjX%HsO1PkoEp-1`8m&f`T^ zmD`=f&W8y6)-6VDJ8$PHq~Xhx2EQFJIV*m6ImeOP;^NFh_+ZL|bS-;-;g!VQZKF5* zebC!cDl|GBGRhj!Ej}N)UcaZ7S_&>o-SQ2m2ykNc>jjGNj|TUc>&#gpCUC#;-2;K> zF|?8Nf_3qjG#(6So<&v|*ga((M1{Gc|L{`R2ik5v{`bCH_zVZXw{FM3N-e--7lf$H zF}pxj@4qFwBz(DuJ*-9E6hX^y?T4;;9T`w6a*R)r0HUev-M^E#f}BzW=ozDcjT;dLC>+MXI2<2k(|<=-9UkQt&@exh;)h?*R|NU)+AL z+cZiPznwz2>&d_U?CX<{2`nD@KO56;M*YDGur$XA%c}R1_DSA$(ZO|dBi>r7Ce${oGJ!N30jGE!Gb2A`kf5&-*65C|G5U@p zS{egTRN$5Klc6YZ#(JT6{hXa#JZ1dk0e|b2p|p>O!2sUBMZ6s40ZPXPdChe6c@eH2 zSYB~a2@o182IG~K7KKVcq+wzryf6q<5)6?9L*XC@R0bj;1DEFg{Q*#{d0=odhDg=l zwkSP$fW4QOn+zE2>+37(3m0|uumeM-rKQ0T7#IcvQ6xZ~{w`i9Kah(j-yaP~tS8z7 z@8*Sfb>ThMh_ZF{_L2uskp6PP+3g>-E}p+>q7Vl5L%D&WqL5>k{uacb|B-X^_Hg>! zI0g;II$@o$E?%A#S?E8qZuYKTuAcU;|Ap#*rvJ?Vg<2h*e{B3mEY8mVnDF#c@u9f! zn~?t~?P=`qh6Nj9Jzc#$&{!283QWFZZ`@=M9$1u@tB0|xtJ9xA>HmSui$L%S-o(3L zTzx%-{zV*%M0sK50hE|Yfgo@Y9BOg4J{F-$=R|JQL11VZ1#6^D1C zOn4fqDf4QmAS9sD5)vSoDD-c*Iyy2ME}mW}7c^D_DG#7fBZ|jkWZ)P{EKEug1A@aP zr9fD?7#t*p#z=smXmOaWG)fF-i^lx9AL)wrK2Ct+`+uzkj4PU=32`$$cmYE{Vd}LQvwiXj}Ag(*K=DGU|9wN`(D?FC!zY`|p+$p7-z6kwKx4 zmxMe3eVhweia)=G@&AF3|CQz6_xsvoDN6sFEcs=|(-r6Ci}JuK*-^OrA94`h3G#4|F@j|{|Ek?60|+a#STlUtzf{hE8ycQ^eZ6X|2I~D&;3iq`Xf#$ zgU8K(>My19PX))iP*i(R>a(F^jS>|V=d=b=$=GjVEsNI782htxGN>kGZTQR2$<2GU zWTx^dB_}oo@p^`=njZMgM1=1x|4$x*rmg zf%&Ae&F{kRa`mTH!i3(`RnntP6+6}r9T+XJgP|=VxV@a|@!<#;?10VMD-1OSgrA~U z)8=iw@_0HIX(yUG9ve8w7UrHvW~L`a;G^Dz2?;I6et9^q^Lw~BGCAtaY*G@b947S9 zgE&oQ$y&P53Sh8FKji$x#MRPBxMX?HUazWmYz&R9QKO##@GsCA3!HZG2`2{!cV}fv zo$JU)iiqj7!8`@d4;#AIik?VcSJyV)z15@3+g_u-hMCvTsv9nxO(-#zOG~+b|0!!m zC3#1ue`-xMF)j8D_q*1yF;DC+jlmP7oq4;TZO(d}T9?SJ^3JGLx{{uLWTX({Dwkf` z@P?u}4O8J08`EWDhLjVY!xzkwpckHL6U|EXl@~P0Rk6YBjAdVrOdL}$wk#$)d}&`S zK)XwU>9$sTnB}F?cjMd2%zSq>qNR;m^_d1~7t1dxXY|#5cu<2?ddKRGUt`T{-5@jd zo$jqeU{2C6RY=L1;!4{^Z7-lqJNjq>?vuUf2L%QEOC{{QB4ZTF0Z}BZsdvIR@HwlA=RUFTOEo4>^lUi@u34&pP5SY zln&RYx|P)%=L46rR`cdoY#Ur3?fdHVXg?#y8y1)SZ~uqO>L#vZ`5- zq^HOo9^H}4ZxS7QX@9Qkp8kAsTt=p9rx|BVEc+rX0GC}GN=9acr^G&#}5T~bJOebh~PxHeG5rgKFcYv&YkcIhHjGHKt z2`vhXDfTqd(9)XJR^1DPBc%}oLmD|S0SCSoB@dz@4~&q>^8wk}EP(ZK^#DZmX+|w6 z6R%{z=n+fLo^*?d_3E)B9QzDA$e-dW>jn!xL?61pnAZ)of~F5uL-6EhK1*jcEjwrR zxeH2_9Y$0qLucMXq4O-sCeaGJ;!lmIHm*bf0eVv35=;QBNWpp}RG0)uJl%4o_WkT0CcP+=l@TfgNk;=Ns zcMs+n`*2E#^kw3$7QMmKhXgp0|D1Wjgl2eqt_d{}#U~IYvWBq(8*4*qnfqKNuN4=VWzIn?7N0XgS$K%{1bx?*MzQu zeS5SsfNG+EsP5+pH0NG1uxThR)LUd3DyYS2GTi3{VADoeYno&>Z#MFX%Kw@;!U2bc70O3>Rwn)+V$F z0|)7e=B@Lr1l>ZpuJV+d@i0#$=+!8$7o#MQofE_;4}6b4NigH z7UkEF2?=)4Mha9&(-Mp#llP=dSuYxR38;8Qk>fyVpCL`x)NHR3oEFqeg`Is;aH8ED z*E9?i7SBoDMoFvQBu=r-IqiFz!;FT={yUu;rRw}zO@&=0r+6B3Q%0_RPn6|@(_2QI z6);S$(O61bAX%C814w01!BP|Uip{>V6^0vO2(bdBMRAwVf^DwC6POVI)KJOXqQc?9 z^?KCuzFmS%{xYe*Cfi=i(NnjG5Xe}+6yDh@0utpGZl~k7{GL3%1g!QN>(U+<1rje` zSsO&-bg{UbH$~wz;U%OzW&!nH(CXw1R-$xy-dQKBv>nn*ul%#3z1AAy$%4`OiBoOV zfAQZ*oHPtEO@{7ukEzT7h(h$l%gL;YlTVX`85YXi$R%pTaV<5C(fmpY`fz8UfTwEb<-g{#qX2?w|eBPbG+wulZnS02hly@ZJu5T=}Ry&<-WV4~(xHUrr z`%C6JdiD~D)3fU(kYt-LNi;Ky24dK%BF6i6$fT#|I?J$p>Vm;N>9yGy=DVX?sy)$n zO-_2;wqp)U&$L-ygh&&NFpc_}?h6%CgQ@;(mGh&WgfbMKDyy8v6L9-^zf+r{fl&zg z^$Iyt9Z|p3&_0~3IesBW3w|fJY|W=M%N{c*&65#TL%)bEBo4u1%@!G{Bo=KIPdr?P zOtLLkW#$PwW0HXb4~wxuI^7};Lm0WV?0exPI={aF6$gY_!KkQv0`_tz)zyFgR9OGE z(i*?cU$=K={~;AVgv$BScIcyK@DKIiV1>EMA6K-noq(pBM`;TJ>yyA`E%4-niIo}RD7!`R$ZrlaxBIgB(sIpoMv>&j_c|> z`@CjYLb7CxrH}dc2qRU00lo9{shmo>+|k^g3MWZnp%kq2JpkxHBNRy5TDVZ`C5kYS ze4P%nny3D&rNrVqy)bzC`S!HTt{w3m#FtCY!h$|v(BP3uOl8RkKL0(c!zlag3mQxc zQ(oJgFGoJK@(RQ7bC_Cwk}8wI#EXcUX+;qCFk@93`(u^W@0XDeXi!6bA@=H+bY8_J zyZQSzNczZhNWO|5vL+bD-Jf~y8hKbyJWCaC$0FRfuibXUF$Qs@0uY8vjcBQ>? zX?Wewls&~ztk&s{Ov-T0g21Wn>z2VK_}|lOQlcnj~i*I z5AzFMp^c<>RCjRH-dwx_@JPJOc#1f5|3(E7n2Nq(aPb_}uD+484Oqi*rKMnS08?Dv z*-FMtrIx6jRC(}2HC7m?K&>XMsz1T`SGr&m_m`km^{@L+qp_d%&;8Z8_mpAaJ z$EciLnyV?zCGC~<$uJbLP7T*=$#%cL@T%1G5Krf> zuJSl7$NIoG$=Ma(sP&eXYCnfjh9voVR+DJEJRft>V(umuJ;k*6%|OQD^SRHlR@85& zo`g!fGr)3N*EP-0Dg@){n*98wf}QGVpJdR?YR}n3c7`yv|1sw3OsCrU^TcQSrBJ>A z!t>qQjP;;Nfy{$^I<-s5Xv8Ruv55=Pa!0eORvuwIlN<#8xUjfrg!E<66_0Brzo0+6 z&P5yYsKP8+l4k*`H|(nlZa& z{ow1w+s9jp)qY=n9V?oO&zo=*8#M1}cvYF?9Ol7B*2eE=HNRxBduk=C;!S$u*Y*g* z%=9^$@B=0Ftg9rEdK{m?c35HtNpGg6qwJ?%l3T?s0u6l6sOX<^-+x6FI0$3bds)Tq z95Q}C?$V>yOFYKtv0cp(SMzYa_jkA#C$l@wF-#ZMU-0u)@!KuctCo_~ z0*99yzisk*J^UzxKaiFsmIg)eQ8uB(IwhP1sN3m4KdNVcZoZ}Id&Qn;<95%XnUQrY z&xC{#Q_Xoh1rdbZEq*1TPij}}{YlI#j$~^@^H0jBrD-q>hX>Amx0dTB|CpX%> z(VGXKXnRh+4Ti`1FesGrJm7K+M=-v-lpf)hRAL{N#>Qhx&0Eqx%P=lRd$O+ak>?dww zgEBL#C zrZmF`$xCgQbWXf`*}+k-!3PoYqk2Cbc&$4Y`F5o9YF}W;%5$sccWe&|%uMV*fw|jm ze}xU>5%jZjm#ir)or!B%KBM~aAZ+GyPSuw7mQ5zJmV}`94_vrp@uzszJKGTIgA1zK zpLv0*7nJ9}E2Xi%x#X#hUSCX4keZgF&Wb(EkLa_)oyq1NTw!Uj*-{d7%^t9p3#)sN z-A#ki?h9T}KG~6j`NH+R2~RbV@$$_e?6RGZP7*JDwd77`WXIj_UtX~`*7lu|<@wg) zd1-HU;Y!>3V86SRr;XG(CrBh&i`hI6dorio1a~lYheZBG6u(O6>_#T*st9-NZrIf% z?u&#J{8^&;Wyd$VoU7p^9fXkWO8hYB(5-mS$Lk9R?~}yXpf`hV*ZPg__-E()Hbny8 zViL?1HE^~*Tje*e_BB2yZ+CU|cGBG)+_T<}l;}Cop*fKGMEiq2hPKxEpeN;ac%_hZ zZgAOfZBWm_X2N67K(08Ufu@0)#o4N_DRZ*2+5Jv8^%_5yFE+tNNLJQjx7=KxR@olO zbr*Ra<&;HeDG%9NjjoY%&x^TY4+rz-r4j)iE(!8v!0+4Yi zSNig3;l*ft*N*nW&Su?6q{LF7kExk8uVD2xE^BEk#MnkIt>fq0MS2;I&sDaI3JgS_ z-M)F?^KEF?zG=`LcN2j#NMMhurbj=HgtI=zKGSK@wFD{)hYC5hB6LY>+}MhNPq*`AZ%qD+ zYF2^Xe3b$+Unp;~_8`WOh2Ed_DpL3Gz5g9{X+t>8S>XVe|UA_KN z;$V)Y(UOmpos+bkQ8<~9`{<(q8Yd(j{k*vG8|K(p zCRBH)=jx+<8DyfF^`v>hvQ`t*!rFA^fTV2i>T;LU0ppxH?$p$E(iRgjh!nspW>IR_ zCGx~rmrcm>Sy{=eS%QU-vsWbfj?;lArV#02$X-;)jP;MYL5ua5Xcp}m^->|!2a1#> z4bP3`SB)`vFE35`^cKy%Wlu^L$+a(erni?NR0>4(T{F0nbk^KQ#dbD5LU5=CyaO~! zVWTk7z51ny(aqtjjRtOQ)|o}e3J3*{b;S(z>*k`r@?xGmxYL!!rXuAHXzgB`Fg2s> zcr2f;2`8A}AK9(hjApYRXb((aAG;!}>!2@IJLk=x&KQ)p?O$;n#5|Z>tyixyB$dgk zH(5h$&wS$hSvWqSf-JYF*GK>D1J^yUpoa`eFXmDAsRQ}$)m-XpKmpv1ZRVc*_@G2X`upbV@a7t49> z8O?$!4i09#XbZ9rUb9#I9C{X%>F(0Z(jQ%mU0o3dRUQT{=#$l1@C9d@SF9}*u|9g~ z^ZhlYYqC7#)VGP!mRtO9bG(~f`hqPj>e~%0c2TWM;5QVDD204u+M?&s+Az0|33wK%9Y4Afw>`L=nrg^lqSChY#GzvRzE-H@w@NmVVtMBJv{UHr(@2C>76VTk&=Y!xGlyf zk8O+3k!CNb$LJ<0l>OcDG7lrF<2&kg>_yub+s0>?Q+=UfFoW&$KC29#x0oHDW_RAO|;goEsk`MH!ZK9o;(!PZ<` zYodKSK+cHYix(jXk$sEEWVVX$jnYbl5#RFH=sxn#v_!AO_BMR2%?Ar>AwVL4Ypsb$;-%-!L~4(|uWbEkVaXy(8n+?45$IbiFayx4p0o=8XG+ zveFCfI?0!ZDp@ArM%t-B72mEBXJ0uR4gSbdt?;>~&m7NZn-t8_gE(;fQ-X&IvKy*$ zxcVz0cc5gMLZ2V8_pO#>+hAzo4uOoE-)X=%S7il3d=Y-GTY5crxq?)z?}a}ppa7MWAz^aH+g^R zGv1SwJdOHl!ILB}MHTCus^HD_-OifE|^V=S6`9kDtWZ5cKg4GVB` zIjQ?HIcBR=LyFXP=2|x8IP@EhMzilvtMIvoQ*M5tU@J==!9E8I%-fA{pwo5RzunNp z$(QmOc^1%OrmvE|-^V$k30T6Z5i zad4n{Ee2tsDe?yiDUHXPca-TsMOrV>{GKQ~SW%AQTK4{F-G^Q61Bn@DKF*U)xz|Y3 zEq)m6WiYzjEA{g>PxwJ$=dQ)yYp{eR6}MdlF?EwBrM}#d8ulcq&URKeKg8nh=KFY{ zLox$|-(RoT`YJH9Jwx5tXd2#*43h!PNKQo- z?rglPRiFneu*J{r$)rvHcqn}g*8dG7m9l+G(w4_q;!Yvi*4B1obv5C8p*A;|5q8kD z%3`MIDVCdu-n(++c=hhN3qU0VOutz#v6{QS6z2=eujUnI4Y@tUb7XP&;oW|YBwwwIf!lszf4YoVZ4S2A#M6*POs{>^T#~kIHJ5pi{!*;;Xln2xf2xT`#X`qPELy*3T_e-i7q@Ye^g)poNKU9$g?+W{h$W>yrYdF4 zCL~AQoMjr=HffxrylYdR(xfxm9LM+EWf96UYlF~vvQgfV!L&Ads9(ctufKP%BL}Na zDR1*NCZFmMx{6AH*>_TeiZDXlkw%TPrGB57U&~J4SvorL)1|b(bk%P3_*X0%D!Rxr IWt)5d2bHp1(f|Me literal 0 HcmV?d00001 diff --git a/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll b/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll new file mode 100644 index 0000000000000000000000000000000000000000..3e3a6fb7c829f3fb32d11d8184600bc557e6c7ad GIT binary patch literal 9216 zcmeHMeQX@Zb$_#WxA#HntUG?_)6&WmC7L2nq)024A}ONyA)U=HiPTpug*x6X$+h-& z&%1jx8OI6*$1YMOc3mVwTqAX%0JV*@jg8uno!WKa28G?$0o>X}iw1J?M}wlsCz3y6 z!?yZ+vv<5B#o8`fpeT?b_sz_EZ{BMEt|0{+tVg>%_&-%VQGCMV_M$M>8Vw#Vp~h1 zNBfBeMSxCUed}jsZ9k-2v>MStv>up5?mg{*fyFl&8;m=zo44 z0BCZqCA-m+MI$C8a&aBc&f@lSf6rq(*`5kO6TyftXuF@m4ifws=Bgy ziv5bhwkZ^%ZY<3A>WGBI{`WyF&`KLRLi9}tz<;6zzK^w_wWGN`xUGIis3i$Rb9=)! zNwJ|SX4f-l0Eju@ET}DwXxF2(tU)7SuLZn-KnOOsui17XjK+s*6s~aLyjb%BQ(7?J z9rgzFYi|EF%nsXdQml5vSKtHD+~`1h;Y#zGh4?#EE6ZHIf*$TfyJ{$hBCsO^w<+(D^}F;`WN` z*RNlTh1=Ayk9ICXf^r`MC-`OXiIz4nSG?OwJ>7n|{1kpG;+e!EV3M-Ht>ofSft#V zzsBlpZ2y>(z(Bhbty1vVyU?zUv~;0r6Ri;^4TkkL?2oOAYL_vvBN%P$NJeB^>TSWi z)g5ahQOVnK8(2@-y8&(N=vd8;){?4`l~mOQ6G5r0J&*|2g#G3QHQa)e0=C}{35xI8 zHK*=p2e;OB1gRHxU;z_~KZz&Hk`?E9Zeqt_=FTfQ=SOGF8QZ3` zZcQ`HS`Gx{_K?$T{7|%>xJgunEIXaCbZG33L!xv-@Od-ao$5$+b#!&gvm`_& za0?-#rVB(rLH!z(G>v(VVNG)=(Q~VaevQ~^IzC2E-Q=IgO^1)C`#^gE{aSdp>5yqp zGBXNdOq|@TM4}v&?};wL@qpok5WJJn!c7OQ&V3N+$aS@~Q;cL*a2gKiir%TH014gB@ z!G|%UcC|jNQX00YxceCX5hOIy0p$`rPxK%Icnto*|0CkCx?lDLF4B7SusQ_q3c4s( z(~m>k3qxjL%W8TPqqv8@`gOV_PAohgd>Ak)9u`Lz^1(}hzY1*ek8pDUO^th zMxlQ|pb1LAKMI`$4A7rS_xu7p3OyBIDno_7D5*b{n6F~2ps$7l*t=0;`x1a5S|{6E z0Ath%xSIAz*dt+D!lM$NmM|lsA>lm|J_^`CKLQl=OS&B1NAJ?0(nA?q7a9h}5ywb~ zRiRV#7=0KRri9oT{w%E%e-+Bn)AWLxN2Xw?_tCB5S#go-X^;9O(u#@jReF&Q28RJ3 z7yhW1FzT;G4w&z&uhLdIs!j5DOa7;6Blvs7?~2dTKKgQi#Ot&Zn1uLNI3&{I{&0;r zD!z{uO^Ds$o5Y*a<~Qj#h^B;iCAc^jcAQymJf*()c}*C zI{F3R26`8;iPkD9+Cf_YyT}IoG+hAfrAvT!(iOllN(NGN7i|X2;si)x5BCMKxEs$0 z9*{D>N%ukWcV&AcHHbsPr6=eadX3(ppJ2AZ1&#&(%t6qg9OfItZ%TMZye4afwpU@V zfs02HAvcjl7k?s$2}B#htb{u*MGHve^N@>$vxDoU@*dzLbD7?&a%>3FnkY@T(I4V@ zo7sr=9oRE5(LK>Y`$io*tGn)j$x`1ZbS@)wQo7$N=5;4CY3k=HJaOWAm=3=Tqqsn&p8L z6Vy{E=vHn>_cFPRm!VNdFJv5jv7av!@^034Ok)!AvSl&7wrT28m76-OTe@RpX~xT& zeMqDS?M$vDym!Vhb979f(jDE(>NKYFs4?7Trap|<89IV7vN5fD6BvxyTb@KzJVEI` z!!6h@&u=CDi<76rx;LE3>(t}AdVbQJmz`caKWSJ%I-Zd=_3`xABbF!hkJUm2vVOuS)86`dn>rIP2+}F zjULZ9FlGQRefqt&bH2jQo~iUOv)-cX+4%~pf6mh_7mHm%m30ijF4(;?F>OuRPCg^U z#QZP0dTndUm@Yb!??w%Mx|?;30t=}SeUHcy$8<9@CtGfXUN#TAAy>?LHxhd7!n|Wl z&s356=L)t{)i29nT^Vb>BI8)m@{GJL@t!ehn1)wH6Yj#A{?PAX@bG?cN7Dc$wb zIA%&xv002f@lKW#F1bs#SBk>2EjTYqhYO9!$#OhbPMjs0s?enqu4*PUWMm!NwWquk z=FsEz@)s1)^R`9)>d0_$3>S!sDz=TczN|}92My~U@?Ae*n>pQKhG|s3oFlqfKxd$6 znq!__=+jLjkJxc2J!-pN>G-!DoD7TmW+|b3^5d-MW2|)gaqnZjPpK^PrLwXr+1qDi zrY)#8vhJc={T;#%kO$Y8?#vpvmP*GJqL`Q4mCM5(cWJ`KrpWni-a!=?xhuNX4-uD^ z9aW1b)uJhtRdaFg$h%R-WX7Wbc{tI?LydE9PC9o^&lWw*w|tn8U6|;<2fG1h2BqC$tm25%pD&=t zrS1gj;Sl19|rhjte4{CUtU@__Sbo(kw`GzFT&Tu}{UTy7!jp@v>LJ`0`# zxQoa*@xHfZ2(^W08hBimfKzxJJQg(QMCl&jb7@(tu~5=cWF6P;V_uU&V4~z{m5TD6b38g`(_R?$Mf+eMdKyt?=z7l$u=2 zSH!e-^wGf+72V)}H31RV&M#+VX5fB7vT^O& z)k0}VE<6Syi&atCL<$Q$3to_tXCoS@r$rs9zCl3^*?NdX{AOS1H)>gE$ucDxE87Y* z#+UFlRg(ypa4bl`OxOwI6>0FsNH~-oOABuq-ufZ=%a zX5;y76NbapF~E|Hh`>t_mreZMF|IV8bTWlu+v=a=dlQ%B_>5!U>k9BGl5a@R#?)~C z_+lp4R=Vx>;}g&BR40VQ>lcZfOqv;)pVspzZIPl$?l|7Y$2`L{wIgm3Uv&h9kpt*R z6^I(~Hn>RBUVU2I)zOKsX||xddoW< zJ5E+Dqm}b~W-jM+nwOcDWAsdRM$0Z{3tGlcqHzeYO`WMue7@q(jeGwDR)oN_FkwNwjIbMbj%f z`hH6<<@^3Ds!XaCS zhu=m&0PTY+8nfWj{*rQRtZ(deB-8Wk%(w1*@#uH&%RYYP_gF^n-ZRHtWb$Xax;wjf zpYb!uGk8<8-7_ES%TK^zPu_i|6j^61-Rs`n`SF8Nh1?{K9qHM1+ioh^{Fn13o8gzC zx9`yA9{a&v8(+NRZ(kmi-!=WIPH|iF+sk43w-g^g(Q7+>ra6=`ERt`Zx-L^pre1G` zjOFfIF7E%J&tLe6))Fl&RqlVcz#h2)&wNMloH|G}Cr>)Ya2p@x=>)!;OaS&HBN)S% zg%KJ?KMgqG|LR6B2Y>#d|6PP5#cy%_83rgU(m$MJSAsr30jYd@58%$_yE~1WmopYF zTji*6P&p@Y${9(HXi&_BIh17Pmse52J61s$*(2QP{zZ5}C&V-K;-W&d)|Uyc7w+wY(T*zRZBIjl?;R#d#iYxhZ4 zh}THHLDuq>s;m=X*1V=Yh#D7K^4NJMyryB-SHH#FxVF++zXhM%131uX!97=X$9?j_ LzvcY@?t%XV?WM^? literal 0 HcmV?d00001 diff --git a/docs/_template/description-generator/plugins/LICENSE b/docs/_template/description-generator/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/description-generator/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LICENSE b/docs/_template/last-modified/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/last-modified/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll b/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll new file mode 100644 index 0000000000000000000000000000000000000000..d91cf08737e9e605e894d2c405a369f5966a9ef6 GIT binary patch literal 11776 zcmeHN4{#h;dH>$t-tOvbUvww=U#!S$J2HnYT_o8tv9TS?`r|0JY}u0RIEc!pyOp%~ z?)JR9XG_FRWKxotffjd$gp@Xr0&P=BNJ=1S(l7%g1-A{*nKFzACcvaaLkoreNlJi; z`}^MR-AS^YDRibY?XdEG`+eX0-uJ$L@9leg9=QF36d@uN_oYijPvgngE(zZp%%VBI z`k6RAS@+_ar^UV(*9=eDo>_98NjFn4vzcPi@y&6|bjwB4E}A_DhRlMKv(ihJ#I6rj z5B3u66A{|>`bU1JGTQfOt+`0F6S0sHaAVzgnz)bQCaRaX64=cQjR9XD!wo)P5jyr> z7Uh4nXGk&&*VDU+4lr_z=y?vp!fTdjF?gToA{t)U_GYwMHC;*>pl_KYpZ2Y3A9VdX z0JKS8L%TuA%Y;>F*K@O=q;1E5u%qj7&$)IXU1`_KI}l`BX%;u@YQ;V0+C>C!=U+bx zIc%FkQEG+m=~YBxzSw`8ibXcis`eYHk>9roa+FV+{EQp=QIOvP$SN2JPpSLOs!#7R86X>wZI%C zDCB944Th44E=xr)S_;J(Yp6@srCJ!#(Xd=LG#D?Xq%+cFgfr$ADh@CO!4wKjYtUaJ z;0Z0ECyW%_(CVDGpncoqrAwDyiq*A(PJ#Z89r(+xA?uejL zY@_Dh!A}ngV=eAk;Lb*go&&16p8|}eP-!uzjoHf`7!zw*=G=fs3X>Sgj;wFokm>;H zbOLPXjHEVk<7Or>ZUgGYN8%CIg!>uji@tv)%#En28zE5*qqhZAOjDh=;i)+{0lNRl z=4=JFNN>Bwy;%thWg9pfH1}@M&f9@6Hr#uG2YO|oob6z1?zbxvOY}Nt2UzQMBY4Ku z)K1XbE^uJ96sk(o-0xS!8`OmA{t%S=Dzk3pp5VMK0?rPAfTOtw8D|R`qKT;c4yJ;E zYnq_;vsEcwbDymeM#2cjc!62L7)D%mc0r$1!<7?Q%T*L&Y9d<2v;fl+YJebkUFfU? zWwZRYGh96z8roI*Gq?#N5eee@B9F*%jkw>g*n@EWka2dPCDc5pd6DiGRk%M55E!ew zA7;kKYK*{&aEz_d>KJ<%4HI<`xOC! z8wB2Y3hkp${U^oX1*JVav{{5mtwydd;*4M*7sXvl9R{``?H$2m_BO^V@okPszYyo$ zL$~f0;hO=*)5*>0_VlLqO&ur{j(#5S8wmNjJBhaIc&*g|AM#zhIO(y(C~Vrr?IT0< z2Uyub(z-n({m2b{8uZ^HTu}LVYT# z(R762k0pFS!W$(#B;nsn_zw!p+!s9!IH8=5#t?}|^*9;otKeK~oQ@{x!}J6e+&hg& zF;K9XRp>7 zB|IqMQ3*2=+7g}vTuMI#)F>f-YIM;`u{N>?mGDq>1f18!QCcH@FFHo&=}*)V>JWOfY&qP5hDTofSM3z#k8>u@P1>pI4>+QLJx}dqD5SwcB56Wo_=vr`g@VK zLC-}xCk~45lOqm`t)gBW2WPu+7@SPBp1u!DN6{-$mj$PwP659gk-13ssr85n+jE`{ zDG!Pnn$aH;uS?E$Wb4ynJL>xLfLsYZVU)gYd`;|=Q5u#p5#lbDl)n%!!_E%zfoNP| zzg8=+(_57FN{(JZ+;TEXZ22bT3A$Zj$!Og+B`?Ry%TWx>y>Z1rHYSxMYGGVS(;|8v zuz_9#TtzV~4tR=u8!$eoJRui+ffWcZC3ziHgp`}FFNskXj(Y<0 zZXxfq>>I@h8tR`)}X5b!@DCtev*+vHU~UMBd8! zR&LOBN|x)}mbY^}Y-YJmmP1?C@??XgcV;1Rtb_V{i{*mlX2$atQwQ9feOA?DXMNi#X51NYVJvMQaB}6mwUdTs zJl`s$`w!p%e3HA4jnSZMl`^h1VELI`#?Mfpig;Pa&D-Npk*)OXcJk~kw0r42R?%|p zEbVh9d+f|)(eZpxQ+^@egX1egvoGWMM_k*thV6ni;fMlEpE z-(!0v$K!BZ$@Xfj*v_hreXdjJv(bY!@6Qx0@_;Z$5)hToF3+^GX zJDx>>hZYTHTu{=9N+{VV!uJ^&S4qHx68vyw($Jdg^0Av6FGH@!8ZS>ya-?d!ii^Xx zKbJk6abaU0J_%W;9QSxlo^w*8;7@ni^PNJCRZ+;ngaaNb=<>$?;)LTCGO~E{SC#5^ ziWBx^*_CpaE9tSktZSE8Nv$z(U-md;eIZeIO zCC8mZS9I`Lnc_@M$Dwl3M@2}!Z;#t~+n>|QB{t$&LsM2h-#cw(IVD^Oa%!XcYa;|4 z1UGEToUGPHI7B)O8YTK&5g?u=JsaY%gB?gE*4Y^Us(%D$A4 z$)1#M3}&*&YxP;cjI%lL>8kTNl%$o%be5|mnnrv(PjjdJAl8j=eRSM(E}xf^uD^)I zg$JU6OwpdOJii|+S2z?6yLN$iw$rVST@1n%6qUrUR7VvnYnIRf=@M6DZCRT)+#Ghe za&itsAE|dbYsoUB0XyqDo-^U6F?+kbZvHR|$%0d?PC~3_9?05|Y$MNR`5eG{V!0CK zxr45@FR3svLw?4sGWabsBU?kGu*RGqB-_dqc-gdE8papN5Dki8cNvQ+8iFkLIg-HW zli>Bw%fQuhQ+-?ERm96??ZrgtEc4P?`3SA5y%W4@?1PEGv5||>kma7V@nQ^@AXF_^ zi^s~Z@LFXy;t{@NWuiHE9*t#kIV%_7lbAWRAbVF#P4MpVXu%rQJMCLVUYM%(*7EP+ zH7Q@=vN*fkN%_)&3a6OO_|zwtEjlp%F1)IU7hHY?9GIBEtB&;Pw_upzsm!Fj?eczi5*~McPIt(UINYO@dPdmBGF?C-F&8dC(sDzzUFO z{seBk9>K|>%?EA*I)JvM3Xo6BKf0?A5;k-M@`2O@C?7l6DfGyqUjcvIv!<^V_yl_9 z@y9l?9pj*R$6Fc8qZw%z-lX82f=9vV+-`$s!2+7>f{nRw^ttwDrR6~cYU5UP-$Ctk z23py=BA`VjXe~>NI6kf%BP;!4U#X=9QXG*s{8gf|19o`Oa1(8Tz77)67XDd4=Q!gR zi8Xn&v1e^)3wlm~JAbSWae#0LeDC3sMt0EKF7|aJJ6r44WfGoPQqu9(HrQ7 z1(v=e^v4IEwRx1HUEl>7_HgDivEvVX=ByT>F^Fsi{=Wrhmv@rr?waTQe%QeAH6iUt zdG?_UDkt^1XyeGa-~=8ZT9Fdb&@l4P@iC!0gE+8`Q^1(>W;MsLVV{ExrDz13dM7-XI}Zk6p@+X9EC>IWkRkKBj4jOsOd{&jQoS9*xcFo3TVMa_J$q;0 zx&3od?Xh#DnnD;@szek>BEe)_GZhg}?zmo$#g`^WMDl3zC_k4b-l@csneY*hCoKGn z5+!HP8TCRmGor>svJL9uQ4{2O2|pAiB_59(_`HEfJZO4YUz(gzl1G&&25VZeqFyLV zle6!Img-|hX(ojg9)&ELl1B{_ts|m=w7@Ra#Ag^e#v@CDrqH-WbL`ba9t-1BhJe{X z6Bevgq-9MJUBvN0QyRws!1o@Joc&^Q_BpBh1$-<{%>Erj&=*LfUY9H+Pt5<5{hOMa zpgMU1u}q#o7y*+f@G)13>!y;N{U(B-M}mG$x}H2Ey_h|loITqlJ#1oAp#+Yzo*kM= z*P#*Ku0UYw@I=UF&&GAhK^RQrY4)s9;jgS$IEoh+MzL9FG1(E49LU+Th$s>Z)h5Qf zt_k|UZ(58j#F2BEhe%8`X%q=ECX#33T40^zH8ml0kPQtno_hD_;gy@ecg}eH=CQjH z-;8ZDp6mF?%76OzC%37nI#LCIq8Sk_fjik~1g;!a;-*-d98lu$pCO0oVDcbWAnMCN z9FZ-@1=aeTP zr%1U0K~3rXy~EYBc58UF+ku~Iwxm0tB;HtUZ#BEidB5yhJBn7>cQg4`bFe&)4L^3z!_IN5xTA>uu}mw2d($lskr)`{ zL_di8XI;40aZ7M<9X8gTq)&+$e%dsqAj_a-gQ_`62Ksu?X>{^l(mqgPEv zx|AEIpdIHUC-p|1AM` ztlM#W^7(-b&i@7eQfXQ8;{mr`YJrZ0VVbY(|2-vS09IgD%y*U7AHv@Daz4x5g8Q~( zL{oA}WDet-0T1KXfib{dIs~jA|I=ze=zhSy;D24wbLvmt2)_LD2nxmtp7`xwUaXR9 ze(u!+LO?6;Ui+{Y;*DNEHU+%L=j(dnUc+eRJ+Fs$-kpd0Vkz;s`c+|7Mv)8Q!k%Bn8&A)+9`xLRhOSqO>igX$g|YK4HKU& z*|u`k{>nM6G75XeC&&4^DrX`rx41TABVUO_aK4y9X7OVH?Bt81%h}08&n<`nULrI~ z*P>4));%&hwbMZ@NBf|M<2V2d=AP~cK{J2wvgmSsVt-TBJ^yWrRvW=DaF%?Ane`4#f%t;W@9J0iRv04=H|1&YlubvBRF2gKUbS}(D xEguALId@$chtI(Ud3>8eEUw-s$e|3