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 { 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/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/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 1fac66ec5..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); @@ -803,7 +805,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 +990,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) @@ -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 ?? ""); 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; } 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.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) 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); 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)); 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); + } + } +}