From dfcb4b39fbc0167f983c725e36f4fcc147e16e34 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Aug 2017 02:41:42 -0300 Subject: [PATCH 1/8] Allow duplicate RequireBotPermissionAttribute --- .../Attributes/Preconditions/RequireBotPermissionAttribute.cs | 3 +-- .../Attributes/Preconditions/RequireUserPermissionAttribute.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 0f865e864..b2cd3811c 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -1,13 +1,12 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { /// /// This attribute requires that the bot has a specified permission in the channel a command is invoked in. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireBotPermissionAttribute : PreconditionAttribute { public GuildPermission? GuildPermission { get; } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index b7729b0c8..f5e3a9fc5 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { From 95b78df9f049e9b4826468d3f1e0e9af7df0167a Mon Sep 17 00:00:00 2001 From: Christopher F <13098994+foxbot@users.noreply.github.com> Date: Thu, 17 Aug 2017 01:43:00 -0400 Subject: [PATCH 2/8] URL-Encode reasons on Kick/Ban (#787) This resolves #784 --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 1fac66ec5..78e768ccd 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -803,7 +803,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={args.Reason}"; + string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); } public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) @@ -988,7 +988,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - reason = string.IsNullOrWhiteSpace(reason) ? "" : $"?reason={reason}"; + reason = string.IsNullOrWhiteSpace(reason) ? "" : $"?reason={Uri.EscapeDataString(reason)}"; await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}{reason}", ids, options: options).ConfigureAwait(false); } public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Rest.ModifyGuildMemberParams args, RequestOptions options = null) From 22b969cbc7de5ab1ee7e7ffc6f3ce71154c67c47 Mon Sep 17 00:00:00 2001 From: Christopher F <13098994+foxbot@users.noreply.github.com> Date: Thu, 17 Aug 2017 01:44:37 -0400 Subject: [PATCH 3/8] Test the Discord.Color structure (#786) This provides tests to ensure the following: - Creating a Color - Creating a Default Color - Accessing a Color's Raw Value - Accessing a Color's translated RGB values --- test/Discord.Net.Tests/Tests.Colors.cs | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/Discord.Net.Tests/Tests.Colors.cs diff --git a/test/Discord.Net.Tests/Tests.Colors.cs b/test/Discord.Net.Tests/Tests.Colors.cs new file mode 100644 index 000000000..591778972 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.Colors.cs @@ -0,0 +1,86 @@ +using System; +using Xunit; + +namespace Discord +{ + public class ColorTests + { + [Fact] + public void Color_New() + { + Assert.Equal(0u, new Color().RawValue); + Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue); + Assert.Equal(uint.MaxValue, new Color(uint.MaxValue).RawValue); + } + public void Color_Default() + { + Assert.Equal(0u, Color.Default.RawValue); + Assert.Equal(0, Color.Default.R); + Assert.Equal(0, Color.Default.G); + Assert.Equal(0, Color.Default.B); + } + [Fact] + public void Color_FromRgb_Byte() + { + Assert.Equal(0xFF0000u, new Color((byte)255, (byte)0, (byte)0).RawValue); + Assert.Equal(0x00FF00u, new Color((byte)0, (byte)255, (byte)0).RawValue); + Assert.Equal(0x0000FFu, new Color((byte)0, (byte)0, (byte)255).RawValue); + Assert.Equal(0xFFFFFFu, new Color((byte)255, (byte)255, (byte)255).RawValue); + } + [Fact] + public void Color_FromRgb_Int() + { + Assert.Equal(0xFF0000u, new Color(255, 0, 0).RawValue); + Assert.Equal(0x00FF00u, new Color(0, 255, 0).RawValue); + Assert.Equal(0x0000FFu, new Color(0, 0, 255).RawValue); + Assert.Equal(0xFFFFFFu, new Color(255, 255, 255).RawValue); + } + [Fact] + public void Color_FromRgb_Int_OutOfRange() + { + Assert.Throws("r", () => new Color(-1024, 0, 0)); + Assert.Throws("r", () => new Color(1024, 0, 0)); + Assert.Throws("g", () => new Color(0, -1024, 0)); + Assert.Throws("g", () => new Color(0, 1024, 0)); + Assert.Throws("b", () => new Color(0, 0, -1024)); + Assert.Throws("b", () => new Color(0, 0, 1024)); + Assert.Throws(() => new Color(-1024, -1024, -1024)); + Assert.Throws(() => new Color(1024, 1024, 1024)); + } + [Fact] + public void Color_FromRgb_Float() + { + Assert.Equal(0xFF0000u, new Color(1.0f, 0, 0).RawValue); + Assert.Equal(0x00FF00u, new Color(0, 1.0f, 0).RawValue); + Assert.Equal(0x0000FFu, new Color(0, 0, 1.0f).RawValue); + Assert.Equal(0xFFFFFFu, new Color(1.0f, 1.0f, 1.0f).RawValue); + } + [Fact] + public void Color_FromRgb_Float_OutOfRange() + { + Assert.Throws("r", () => new Color(-2.0f, 0, 0)); + Assert.Throws("r", () => new Color(2.0f, 0, 0)); + Assert.Throws("g", () => new Color(0, -2.0f, 0)); + Assert.Throws("g", () => new Color(0, 2.0f, 0)); + Assert.Throws("b", () => new Color(0, 0, -2.0f)); + Assert.Throws("b", () => new Color(0, 0, 2.0f)); + Assert.Throws(() => new Color(-2.0f, -2.0f, -2.0f)); + Assert.Throws(() => new Color(2.0f, 2.0f, 2.0f)); + } + [Fact] + public void Color_Red() + { + Assert.Equal(0xAF, new Color(0xAF1390).R); + } + [Fact] + public void Color_Green() + { + Assert.Equal(0x13, new Color(0xAF1390).G); + } + [Fact] + public void Color_Blue() + { + Assert.Equal(0x90, new Color(0xAF1390).B); + } + } +} From 57a461c9ffe880d06f3b23419180c3ee0eac40c8 Mon Sep 17 00:00:00 2001 From: Jay Malhotra Date: Thu, 17 Aug 2017 06:47:00 +0100 Subject: [PATCH 4/8] NullOrEmpty -> NullOrWhiteSpace (#758) Seeing as D.NET will warn you about an impending BadRequest if you try and send an empty field, why not make it warn about the impending BadRequest if you try and send a whitespace field? --- src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 7b0285891..526e66a5b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -249,7 +249,7 @@ namespace Discord get => _field.Name; set { - if (string.IsNullOrEmpty(value)) throw new ArgumentException($"Field name must not be null or empty.", 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)); _field.Name = value; } From 6b5a6e7f1fba738d13a465977b5c4ac7cf107d14 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 17 Aug 2017 01:47:37 -0400 Subject: [PATCH 5/8] Fix everyone mention. (#755) * Update RestRole.cs Fix everyone mention. * Update SocketRole.cs Fix everyone mention. * I'm good at this, I swear. --- src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 2 +- src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 9807b8357..486f41b9e 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -19,7 +19,7 @@ namespace Discord.Rest public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; - public string Mention => MentionUtils.MentionRole(Id); + public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, id) diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 7d24d8e1c..c366258cc 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -23,7 +23,7 @@ namespace Discord.WebSocket public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public bool IsEveryone => Id == Guild.Id; - public string Mention => MentionUtils.MentionRole(Id); + public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); public IEnumerable Members => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); From f9970891742775253b453a2b8532dbb7b2f5c7d1 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Aug 2017 02:53:34 -0300 Subject: [PATCH 6/8] Try to pull DM channels from cache on CHANNEL_CREATE --- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index b13ceca1d..5256e0efd 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -699,7 +699,12 @@ namespace Discord.WebSocket } } else + { + channel = State.GetChannel(data.Id); + if (channel != null) + return; //Discord may send duplicate CHANNEL_CREATEs for DMs channel = AddPrivateChannel(data, State) as SocketChannel; + } if (channel != null) await TimedInvokeAsync(_channelCreatedEvent, nameof(ChannelCreated), channel).ConfigureAwait(false); From 506a6c96c9d7336f87be6ad46d654958fe6249e9 Mon Sep 17 00:00:00 2001 From: Christopher F <13098994+foxbot@users.noreply.github.com> Date: Thu, 17 Aug 2017 01:59:58 -0400 Subject: [PATCH 7/8] Throw when attempting to add or remove a member's EveryoneRole (#781) * Throw when attempting to add or remove a member's EveryoneRole This resolves #780 * Removed braces --- src/Discord.Net.Core/Utils/Preconditions.cs | 8 ++++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 705a15249..bec8de9dc 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -192,5 +192,13 @@ namespace Discord throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); } } + public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) + { + 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); + } + } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 78e768ccd..a6c42782a 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -392,6 +392,7 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); + Preconditions.NotEqual(roleId, guildId, nameof(roleId), "The Everyone role cannot be added to a user."); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); @@ -402,6 +403,7 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); + Preconditions.NotEqual(roleId, guildId, nameof(roleId), "The Everyone role cannot be removed from a user."); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); @@ -1000,6 +1002,8 @@ namespace Discord.API bool isCurrentUser = userId == CurrentUserId; + if (args.RoleIds.IsSpecified) + Preconditions.NotEveryoneRole(args.RoleIds.Value, guildId, nameof(args.RoleIds)); if (isCurrentUser && args.Nickname.IsSpecified) { var nickArgs = new Rest.ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); From 865080add9f0c8b5d761f5ce5f5cdd16d4954e8c Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 17 Aug 2017 02:19:16 -0400 Subject: [PATCH 8/8] Fix CreateGuildAsync not sending icon stream. (#768) * Fix CreateGuildAsync not doing anything with the input stream for the guild icon. Also fixes an issue with potential stream types that throw a NotSupportedException when checking its properties. [Apparently, they exist.](https://github.com/dotnet/corefx/blob/master/src/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseStream.cs) * Merged with old method * Removed duplicate decl --- src/Discord.Net.Rest/ClientHelper.cs | 3 +++ .../Net/Converters/ImageConverter.cs | 25 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 8bc800a7d..2f05d5d36 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -120,6 +120,9 @@ namespace Discord.Rest string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) { var args = new CreateGuildParams(name, region.Id); + if (jpegIcon != null) + args.Icon = new API.Image(jpegIcon); + var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); return RestGuild.Create(client, model); } diff --git a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs index f4d591d7e..a5a440d8b 100644 --- a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs @@ -1,5 +1,6 @@ -using Newtonsoft.Json; -using System; +using System; +using System.IO; +using Newtonsoft.Json; using Model = Discord.API.Image; namespace Discord.Net.Converters @@ -23,10 +24,24 @@ namespace Discord.Net.Converters if (image.Stream != null) { - byte[] bytes = new byte[image.Stream.Length - image.Stream.Position]; - image.Stream.Read(bytes, 0, bytes.Length); + byte[] bytes; + int length; + if (image.Stream.CanSeek) + { + bytes = new byte[image.Stream.Length - image.Stream.Position]; + length = image.Stream.Read(bytes, 0, bytes.Length); + } + else + { + var cloneStream = new MemoryStream(); + image.Stream.CopyTo(cloneStream); + bytes = new byte[cloneStream.Length]; + cloneStream.Position = 0; + cloneStream.Read(bytes, 0, bytes.Length); + length = (int)cloneStream.Length; + } - string base64 = Convert.ToBase64String(bytes); + string base64 = Convert.ToBase64String(bytes, 0, length); writer.WriteValue($"data:image/jpeg;base64,{base64}"); } else if (image.Hash != null)