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);
+ }
+ }
+}