From 652fd30f65847ed37f6bd7be536f372f29aa6810 Mon Sep 17 00:00:00 2001 From: ObsidianMinor Date: Tue, 16 May 2017 13:27:43 -0500 Subject: [PATCH 01/33] Fixed RestGuildUser not updating RestUser properties --- src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index f6db057f2..ce8cc255e 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -46,6 +46,7 @@ namespace Discord.Rest } internal void Update(Model model) { + base.Update(model.User); if (model.JoinedAt.IsSpecified) _joinedAtTicks = model.JoinedAt.Value.UtcTicks; if (model.Nick.IsSpecified) From 263864f5792eae5bc3545bdec11eab71b9983f2a Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 11 Jul 2017 12:12:07 -0400 Subject: [PATCH 02/33] Bumped version to 1.0.2 --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 6dc4bb140..72b92fd46 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 1.0.1 + 1.0.2 RogueException discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 864083599..b389fa2e4 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 1.0.1$suffix$ + 1.0.2$suffix$ Discord.Net RogueException RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From 710e1824bfc0b4895531a2052d679aee7e4d62fb Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 14 Jul 2017 15:31:30 -0300 Subject: [PATCH 03/33] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index ebd70cd5a..3f78126e5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 RogueException +Copyright (c) 2015-2017 Discord.Net Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 0a41694f011a8fdf4cd1ecae44f31414bf71222c Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 14 Jul 2017 15:32:24 -0300 Subject: [PATCH 04/33] Update Discord.Net.nuspec --- src/Discord.Net/Discord.Net.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index b389fa2e4..48fe1711d 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -4,7 +4,7 @@ Discord.Net 1.0.2$suffix$ Discord.Net - RogueException + Discord.Net Contributors RogueException An asynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components. discord;discordapp @@ -38,4 +38,4 @@ - \ No newline at end of file + From 0bdc2455bc71e71e3fe2e67d3ca3fc5317bcf0d2 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Sun, 16 Jul 2017 17:32:31 +0200 Subject: [PATCH 05/33] Add line to show subscribing to CommandService#Log (#756) --- docs/guides/getting_started/samples/intro/structure.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 706d0a38d..00ce7a6c9 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -39,6 +39,9 @@ class Program // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance }); + // Subscribe the logging handler to both the client and the CommandService. + _client.Log += Logger; + _commands.Log += Logger; } // Example of a logging handler. This can be re-used by addons @@ -77,9 +80,6 @@ class Program private async Task MainAsync() { - // Subscribe the logging handler. - _client.Log += Logger; - // Centralize the logic for commands into a seperate method. await InitCommands(); From b59c48b9ec5ee2ceb0fdec01fc03d96e28467e6e Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Sun, 16 Jul 2017 23:32:49 +0800 Subject: [PATCH 06/33] Added IServiceProvider (#753) --- docs/guides/commands/samples/typereader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/commands/samples/typereader.cs b/docs/guides/commands/samples/typereader.cs index b21e6c15a..d2864a4c7 100644 --- a/docs/guides/commands/samples/typereader.cs +++ b/docs/guides/commands/samples/typereader.cs @@ -4,12 +4,12 @@ using Discord.Commands; public class BooleanTypeReader : TypeReader { - public override Task Read(CommandContext context, string input) + public override Task Read(ICommandContext context, string input, IServiceProvider services) { bool result; if (bool.TryParse(input, out result)) return Task.FromResult(TypeReaderResult.FromSuccess(result)); - - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input could not be parsed as a boolean.")) + + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input could not be parsed as a boolean.")); } -} \ No newline at end of file +} From c4dcb9dc17762b4f1758e46e6b7d6324955bbca8 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sun, 16 Jul 2017 11:33:01 -0400 Subject: [PATCH 07/33] Update client.cs (#752) * Update client.cs Let's not have the client be a local variable, hm? * Update complete.cs * Update complete.cs * Update client.cs and complete.cs Let's not have the client be a local variable, hm? --- .../guides/getting_started/samples/intro/client.cs | 11 ++++++----- .../getting_started/samples/intro/complete.cs | 14 ++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/client.cs b/docs/guides/getting_started/samples/intro/client.cs index ea7c91932..a73082052 100644 --- a/docs/guides/getting_started/samples/intro/client.cs +++ b/docs/guides/getting_started/samples/intro/client.cs @@ -1,16 +1,17 @@ // Program.cs using Discord.WebSocket; // ... +private DiscordSocketClient _client; public async Task MainAsync() { - var client = new DiscordSocketClient(); + _client = new DiscordSocketClient(); - client.Log += Log; + _client.Log += Log; string token = "abcdefg..."; // Remember to keep this private! - await client.LoginAsync(TokenType.Bot, token); - await client.StartAsync(); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); // Block this task until the program is closed. await Task.Delay(-1); -} \ No newline at end of file +} diff --git a/docs/guides/getting_started/samples/intro/complete.cs b/docs/guides/getting_started/samples/intro/complete.cs index b59b6b4d9..23b59ce6f 100644 --- a/docs/guides/getting_started/samples/intro/complete.cs +++ b/docs/guides/getting_started/samples/intro/complete.cs @@ -7,19 +7,21 @@ namespace MyBot { public class Program { + private DiscordSocketClient _client; + public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); public async Task MainAsync() { - var client = new DiscordSocketClient(); + _client = new DiscordSocketClient(); - client.Log += Log; - client.MessageReceived += MessageReceived; + _client.Log += Log; + _client.MessageReceived += MessageReceived; string token = "abcdefg..."; // Remember to keep this private! - await client.LoginAsync(TokenType.Bot, token); - await client.StartAsync(); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); // Block this task until the program is closed. await Task.Delay(-1); @@ -39,4 +41,4 @@ namespace MyBot return Task.CompletedTask; } } -} \ No newline at end of file +} From dfcb4b39fbc0167f983c725e36f4fcc147e16e34 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Aug 2017 02:41:42 -0300 Subject: [PATCH 08/33] 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 09/33] 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 10/33] 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 11/33] 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 12/33] 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 13/33] 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 14/33] 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 15/33] 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) From 361bfc1a90dbe75d446ed5ce95666d57d526cc61 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 21 Jul 2017 21:35:11 -0300 Subject: [PATCH 16/33] Bumped version to 1.1.0-alpha --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 72b92fd46..4ee3ada7e 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 1.0.2 + 1.1.0-alpha RogueException discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 48fe1711d..6c971fda1 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 1.0.2$suffix$ + 1.1.0-alpha$suffix$ Discord.Net Discord.Net Contributors RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From 182f00f8ce51d3e350d1793dc016474751f63f75 Mon Sep 17 00:00:00 2001 From: Christopher F <13098994+foxbot@users.noreply.github.com> Date: Mon, 28 Aug 2017 16:45:53 -0400 Subject: [PATCH 17/33] Reworked IChannel.IsNsfw to support the new API flag (#771) IChannel.IsNsfw will now return false when being used on any channel that is not an ITextChannel. When being used on an ITextChannel, this will now account for the API flag, and fall back to the channel name. (this is gross design, thanks discord) --- src/Discord.Net.Rest/API/Common/Channel.cs | 2 ++ src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/RestChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 4 ++++ src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs | 2 +- .../Entities/Channels/SocketTextChannel.cs | 5 +++++ 6 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 56a24a1f4..608ddcf66 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -29,6 +29,8 @@ namespace Discord.API public Optional Topic { get; set; } [JsonProperty("last_pin_timestamp")] public Optional LastPinTimestamp { get; set; } + [JsonProperty("nsfw")] + public Optional Nsfw { get; set; } //VoiceChannel [JsonProperty("bitrate")] diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 6b7dca3a9..30c28bacb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -290,8 +290,8 @@ namespace Discord.Rest return author; } - public static bool IsNsfw(IChannel channel) => - IsNsfw(channel.Name); + public static bool IsNsfw(IChannel channel) + => IsNsfw(channel.Name); public static bool IsNsfw(string channelName) => channelName == "nsfw" || channelName.StartsWith("nsfw-"); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 7291b591e..9e4c97cfa 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -46,7 +46,7 @@ namespace Discord.Rest //IChannel string IChannel.Name => null; - bool IChannel.IsNsfw => ChannelHelper.IsNsfw(this); + bool IChannel.IsNsfw => false; Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index d7405fb4a..3353dc7b0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -15,6 +15,8 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); + internal bool Nsfw { get; private set; } + internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) { @@ -30,6 +32,7 @@ namespace Discord.Rest base.Update(model); Topic = model.Topic.Value; + Nsfw = model.Nsfw.GetValueOrDefault(); } public async Task ModifyAsync(Action func, RequestOptions options = null) @@ -149,5 +152,6 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } + bool IChannel.IsNsfw => Nsfw || ChannelHelper.IsNsfw(this); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 42c4156f3..b0d7261fd 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -41,7 +41,7 @@ namespace Discord.WebSocket //IChannel string IChannel.Name => null; - bool IChannel.IsNsfw => ChannelHelper.IsNsfw(this); + bool IChannel.IsNsfw => false; Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index c22523e00..1b351aae4 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; } + internal bool Nsfw { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); @@ -41,6 +42,7 @@ namespace Discord.WebSocket base.Update(state, model); Topic = model.Topic.Value; + Nsfw = model.Nsfw.GetValueOrDefault(); } public Task ModifyAsync(Action func, RequestOptions options = null) @@ -144,5 +146,8 @@ namespace Discord.WebSocket => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); + + // IChannel + bool IChannel.IsNsfw => Nsfw || ChannelHelper.IsNsfw(this); } } \ No newline at end of file From 1ffcd4bfa713b6bdedf238a4845880d79ec4fc5b Mon Sep 17 00:00:00 2001 From: Christopher F <13098994+foxbot@users.noreply.github.com> Date: Mon, 28 Aug 2017 16:49:16 -0400 Subject: [PATCH 18/33] Changed Guild#DefaultChannel to resolve the first accessible channel (#777) * Changed Guild#DefaultChannel to resolve the first accessible channel Resolves #776 This change is inline with hammerandchisel/discord-api-docs#329 RestGuild#DefaultChannelId is now obsolete and will throw a NotSupportedException. * RestGuild#DefaultChannelId will fall back to the guild ID Adding an exception here would be a breaking change, so this was agreed to fall back to the previous behavior, which would just return the guild ID. --- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 10 ++++++++-- .../Entities/Guilds/SocketGuild.cs | 7 +++++-- test/Discord.Net.Tests/Tests.Channels.cs | 2 +- test/Discord.Net.Tests/Tests.Migrations.cs | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 11971a5c1..0ad76c61a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -33,6 +33,8 @@ namespace Discord.Rest internal bool Available { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + [Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] public ulong DefaultChannelId => Id; public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); @@ -185,8 +187,12 @@ namespace Discord.Rest } public async Task GetDefaultChannelAsync(RequestOptions options = null) { - var channel = await GuildHelper.GetChannelAsync(this, Discord, DefaultChannelId, options).ConfigureAwait(false); - return channel as RestTextChannel; + var channels = await GetTextChannelsAsync(options).ConfigureAwait(false); + var user = await GetCurrentUserAsync(options).ConfigureAwait(false); + return channels + .Where(c => user.GetPermissions(c).ReadMessages) + .OrderBy(c => c.Position) + .FirstOrDefault(); } public async Task GetEmbedChannelAsync(RequestOptions options = null) { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index aae18be36..f973df468 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -54,7 +54,6 @@ namespace Discord.WebSocket public string SplashId { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public SocketTextChannel DefaultChannel => GetTextChannel(Id); public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); public bool HasAllMembers => MemberCount == DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted; @@ -62,6 +61,10 @@ namespace Discord.WebSocket public Task SyncPromise => _syncPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task; public IAudioClient AudioClient => _audioClient; + public SocketTextChannel DefaultChannel => TextChannels + .Where(c => CurrentUser.GetPermissions(c).ReadMessages) + .OrderBy(c => c.Position) + .FirstOrDefault(); public SocketVoiceChannel AFKChannel { get @@ -606,7 +609,7 @@ namespace Discord.WebSocket ulong? IGuild.AFKChannelId => AFKChannelId; IAudioClient IGuild.AudioClient => null; bool IGuild.Available => true; - ulong IGuild.DefaultChannelId => Id; + ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; ulong? IGuild.EmbedChannelId => EmbedChannelId; IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index d81d28f3e..b528ca5fb 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -64,7 +64,7 @@ namespace Discord var text5 = textChannels.Where(x => x.Name == "text5").FirstOrDefault(); Assert.NotNull(text1); - Assert.True(text1.Id == guild.DefaultChannelId); + //Assert.True(text1.Id == guild.DefaultChannelId); Assert.Equal(text1.Position, 1); Assert.Equal(text1.Topic, "Topic1"); diff --git a/test/Discord.Net.Tests/Tests.Migrations.cs b/test/Discord.Net.Tests/Tests.Migrations.cs index e786329cd..23e55a737 100644 --- a/test/Discord.Net.Tests/Tests.Migrations.cs +++ b/test/Discord.Net.Tests/Tests.Migrations.cs @@ -57,7 +57,7 @@ namespace Discord foreach (var channel in textChannels) { - if (channel.Id != guild.DefaultChannelId) + //if (channel.Id != guild.DefaultChannelId) await channel.DeleteAsync(); } foreach (var channel in voiceChannels) From 3c1e76615e1ef37cf3deb9aa9ab3e3b2f2064e41 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Mon, 28 Aug 2017 17:21:19 -0400 Subject: [PATCH 19/33] Preemptive Ratelimits should be logged under Verbose --- src/Discord.Net.Rest/BaseDiscordClient.cs | 2 +- src/Discord.Net.Webhook/DiscordWebhookClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index ed12ff383..87f974dc2 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -42,7 +42,7 @@ namespace Discord.Rest ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { if (info == null) - await _restLogger.WarningAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); else await _restLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); }; diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 9695099ee..3d8307da4 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -36,7 +36,7 @@ namespace Discord.Webhook ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { if (info == null) - await _restLogger.WarningAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); else await _restLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); }; From 4205d68b5ae0e3a882fd920e76eb806a176ca157 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 16:26:31 -0400 Subject: [PATCH 20/33] Don't throw when receiving a presence for a user that doesn't exist (#746) --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index f973df468..e4e52bbff 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -160,8 +160,6 @@ namespace Discord.WebSocket { if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member)) member.Update(state, model.Presences[i], true); - else - Debug.Assert(false); } } _members = members; @@ -245,8 +243,6 @@ namespace Discord.WebSocket { if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member)) member.Update(state, model.Presences[i], true); - else - Debug.Assert(false); } } _members = members; From cb0ff7817d066733c01fe66080df9038445e329b Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Tue, 29 Aug 2017 16:38:11 -0400 Subject: [PATCH 21/33] Add NullableTypeReader (#785) * Add NullableTypeReader. Primitives now also load a NullableTypeReader and any value types that get a typereader added will also have a NullableTypeReader added for it. * Remove unnecessary null check. * Added docstrings. --- src/Discord.Net.Commands/CommandService.cs | 32 +++++++++++++---- .../Readers/NullableTypeReader.cs | 34 +++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 src/Discord.Net.Commands/Readers/NullableTypeReader.cs diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 6ea2abcf3..0a9edb5b8 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { @@ -57,7 +56,10 @@ namespace Discord.Commands _defaultTypeReaders = new ConcurrentDictionary(); foreach (var type in PrimitiveParsers.SupportedTypes) + { _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); + _defaultTypeReaders[typeof(Nullable<>).MakeGenericType(type)] = NullableTypeReader.Create(type, _defaultTypeReaders[type]); + } _defaultTypeReaders[typeof(string)] = new PrimitiveTypeReader((string x, out string y) => { y = x; return true; }, 0); @@ -190,17 +192,35 @@ 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. + /// + /// The object type to be read by the . + /// An instance of the to be added. public void AddTypeReader(TypeReader reader) - { - var readers = _typeReaders.GetOrAdd(typeof(T), x => new ConcurrentDictionary()); - readers[reader.GetType()] = reader; - } + => 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. + /// + /// 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 (type.GetTypeInfo().IsValueType) + AddNullableTypeReader(type, reader); } + internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) + { + var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary()); + var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader); + readers[nullableReader.GetType()] = nullableReader; + } internal IDictionary GetTypeReaders(Type type) { if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) diff --git a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs new file mode 100644 index 000000000..07976fb69 --- /dev/null +++ b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + internal static class NullableTypeReader + { + public static TypeReader Create(Type type, TypeReader reader) + { + var constructor = typeof(NullableTypeReader<>).MakeGenericType(type).GetTypeInfo().DeclaredConstructors.First(); + return (TypeReader)constructor.Invoke(new object[] { reader }); + } + } + + internal class NullableTypeReader : TypeReader + where T : struct + { + private readonly TypeReader _baseTypeReader; + + public NullableTypeReader(TypeReader baseTypeReader) + { + _baseTypeReader = baseTypeReader; + } + + public override async Task Read(ICommandContext context, string input, IServiceProvider services) + { + if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) + return TypeReaderResult.FromSuccess(new T?()); + return await _baseTypeReader.Read(context, input, services); ; + } + } +} From 9c81ab9fe492e5548f0b3c84125df2b3856b618f Mon Sep 17 00:00:00 2001 From: Izumemori Date: Tue, 29 Aug 2017 22:45:30 +0200 Subject: [PATCH 22/33] changed NameAttribute to work for parameter (#765) --- src/Discord.Net.Commands/Attributes/NameAttribute.cs | 2 +- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/NameAttribute.cs b/src/Discord.Net.Commands/Attributes/NameAttribute.cs index 6881d1e2e..0a5156fee 100644 --- a/src/Discord.Net.Commands/Attributes/NameAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/NameAttribute.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Commands { // Override public name of command/module - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter)] public class NameAttribute : Attribute { public string Text { get; } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 6fae719ee..5a3a1f25a 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -254,6 +254,9 @@ namespace Discord.Commands case ParameterPreconditionAttribute precon: builder.AddPrecondition(precon); break; + case NameAttribute name: + builder.Name = name.Text; + break; case RemainderAttribute _: if (position != count - 1) throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); From 0fdb2d2c3bb3e1e1ca6f657c62ec448a43a6f6cd Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 16:49:14 -0400 Subject: [PATCH 23/33] Bump version to 2.0.0-alpha --- Discord.Net.targets | 2 +- src/Discord.Net/Discord.Net.nuspec | 38 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Discord.Net.targets b/Discord.Net.targets index 4ee3ada7e..95eccd790 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 1.1.0-alpha + 2.0.0-alpha RogueException discord;discordapp diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 6c971fda1..309532615 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 1.1.0-alpha$suffix$ + 2.0.0-alpha$suffix$ Discord.Net Discord.Net Contributors RogueException @@ -13,28 +13,28 @@ false - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + From e991715bac7643d41bef183cb0374b7b8dec2c44 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 16:50:32 -0400 Subject: [PATCH 24/33] Added CommandService.CommandExecuted (#747) --- src/Discord.Net.Commands/CommandService.cs | 3 +++ src/Discord.Net.Commands/Info/CommandInfo.cs | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 0a9edb5b8..cf2b93277 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -16,6 +16,9 @@ namespace Discord.Commands public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + public event Func CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } + internal readonly AsyncEvent> _commandExecutedEvent = new AsyncEvent>(); + private readonly SemaphoreSlim _moduleLock; private readonly ConcurrentDictionary _typedModuleDefs; private readonly ConcurrentDictionary> _typeReaders; diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index ebef80baf..c94be525f 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -188,17 +188,22 @@ namespace Discord.Commands if (task is Task resultTask) { var result = await resultTask.ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); if (result is RuntimeResult execResult) return execResult; } else if (task is Task execTask) { - return await execTask.ConfigureAwait(false); + var result = await execTask.ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + return result; } else await task.ConfigureAwait(false); - return ExecuteResult.FromSuccess(); + var executeResult = ExecuteResult.FromSuccess(); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, executeResult).ConfigureAwait(false); + return executeResult; } catch (Exception ex) { From 94f7dd2ab8ef35ddd901e4ab3ea6453986b2c7d6 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Wed, 30 Aug 2017 05:51:41 +0900 Subject: [PATCH 25/33] Remove EmbedBuilder -> Embed implicit conversion (#801) --- src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 526e66a5b..7a111d34b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -234,7 +234,6 @@ namespace Discord return _embed; } - public static implicit operator Embed(EmbedBuilder builder) => builder?.Build(); } public class EmbedFieldBuilder From 0de5d5b02ba5bccd92109ea76fb8e76c522415a2 Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Wed, 30 Aug 2017 04:57:47 +0800 Subject: [PATCH 26/33] Added SendFileAsync (#774) --- .../Extensions/UserExtensions.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 0861ed33e..eac25391e 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,9 +1,13 @@ using System.Threading.Tasks; +using System.IO; namespace Discord { public static class UserExtensions { + /// + /// Sends a message to the user via DM. + /// public static async Task SendMessageAsync(this IUser user, string text, bool isTTS = false, @@ -12,5 +16,33 @@ namespace Discord { return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); } + + /// + /// Sends a file to the user via DM. + /// + public static async Task SendFileAsync(this IUser user, + Stream stream, + string filename, + string text = null, + bool isTTS = false, + RequestOptions options = null + ) + { + return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); + } + +#if FILESYSTEM + /// + /// Sends a file to the user via DM. + /// + public static async Task SendFileAsync(this IUser user, + string filePath, + string text = null, + bool isTTS = false, + RequestOptions options = null) + { + return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); + } +#endif } } From 5d7f2fc7ec9d352290538ddf1feec1696209a8e3 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 17:09:28 -0400 Subject: [PATCH 27/33] Add GetReactionUsersAsync(IEmote) overload (#731) * Add GetReactionUsersAsync(IEmote) overload Resolves #730 * Remove obsolete GetReactionUsersAsync(string) overload --- src/Discord.Net.Core/Entities/Messages/IUserMessage.cs | 3 ++- src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 3 ++- src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs | 7 +++---- src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs | 5 ++--- .../Entities/Messages/SocketUserMessage.cs | 5 ++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 61f908394..52df187f8 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -22,7 +22,8 @@ namespace Discord Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null); /// Removes all reactions from this message. Task RemoveAllReactionsAsync(RequestOptions options = null); - Task> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = null, 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); /// Transforms this message's text into a human readable form by resolving its tags. string Resolve( diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index ccb683d1f..47bb6f926 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -43,11 +43,12 @@ namespace Discord.Rest await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options); } - public static async Task> GetReactionUsersAsync(IMessage msg, string emoji, + public static async Task> GetReactionUsersAsync(IMessage msg, IEmote emote, Action func, 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(); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index c79c67b38..e5eed874e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -136,10 +136,9 @@ namespace Discord.Rest => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - - public Task> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId ?? Optional.Create(); }, 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 Task PinAsync(RequestOptions options = null) => MessageHelper.PinAsync(this, Discord, options); diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs index 91a8d7b31..bc175160d 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs @@ -108,9 +108,8 @@ namespace Discord.Rpc => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - - public Task> GetReactionUsersAsync(string emoji, int limit, ulong? afterUserId, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId.HasValue ? afterUserId.Value : Optional.Create(); }, 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 Task PinAsync(RequestOptions options) => MessageHelper.PinAsync(this, Discord, options); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 40588e55a..b240645e5 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -130,9 +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(string emoji, int limit = 100, ulong? afterUserId = null, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emoji, x => { x.Limit = limit; x.AfterUserId = afterUserId.HasValue ? afterUserId.Value : Optional.Create(); }, 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 Task PinAsync(RequestOptions options = null) => MessageHelper.PinAsync(this, Discord, options); From 608bc359ee01fc801ef226e153869b30d81f24b4 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 17:26:36 -0400 Subject: [PATCH 28/33] Removed IChannel#Nsfw, added to ITextChannel --- .../Attributes/Preconditions/RequireNsfwAttribute.cs | 2 +- src/Discord.Net.Core/Entities/Channels/IChannel.cs | 5 +---- src/Discord.Net.Core/Entities/Channels/ITextChannel.cs | 3 +++ src/Discord.Net.Rest/Entities/Channels/RestChannel.cs | 1 - .../Entities/Channels/RestTextChannel.cs | 6 +++--- .../Entities/Channels/RpcVirtualMessageChannel.cs | 1 - src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs | 1 - src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs | 2 ++ .../Entities/Channels/SocketChannel.cs | 1 - .../Entities/Channels/SocketTextChannel.cs | 9 ++++----- 10 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index 94235b1ae..b3cf25365 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -11,7 +11,7 @@ namespace Discord.Commands { public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) { - if (context.Channel.IsNsfw) + if (context.Channel is ITextChannel text && text.IsNsfw) return Task.FromResult(PreconditionResult.FromSuccess()); else return Task.FromResult(PreconditionResult.FromError("This command may only be invoked in an NSFW channel.")); diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index fbb979951..ea930e112 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -7,10 +7,7 @@ namespace Discord { /// Gets the name of this channel. string Name { get; } - - /// Checks if the channel is NSFW. - bool IsNsfw { get; } - + /// Gets a collection of all users in this channel. IAsyncEnumerable> GetUsersAsync(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 038faf6bc..b2b7e491f 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -5,6 +5,9 @@ namespace Discord { public interface ITextChannel : IMessageChannel, IMentionable, IGuildChannel { + /// Checks if the channel is NSFW. + bool IsNsfw { get; } + /// Gets the current topic for this text channel. string Topic { get; } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 9e4c97cfa..bc521784d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -46,7 +46,6 @@ namespace Discord.Rest //IChannel string IChannel.Name => null; - bool IChannel.IsNsfw => false; Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overriden diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 3353dc7b0..8a096302b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -15,7 +15,8 @@ namespace Discord.Rest public string Mention => MentionUtils.MentionChannel(Id); - internal bool Nsfw { get; private set; } + private bool _nsfw; + public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) @@ -32,7 +33,7 @@ namespace Discord.Rest base.Update(model); Topic = model.Topic.Value; - Nsfw = model.Nsfw.GetValueOrDefault(); + _nsfw = model.Nsfw.GetValueOrDefault(); } public async Task ModifyAsync(Action func, RequestOptions options = null) @@ -152,6 +153,5 @@ namespace Discord.Rest else return AsyncEnumerable.Empty>(); } - bool IChannel.IsNsfw => Nsfw || ChannelHelper.IsNsfw(this); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 775f2ea82..7043c8c76 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -99,7 +99,6 @@ namespace Discord.Rest //IChannel string IChannel.Name { get { throw new NotSupportedException(); } } - bool IChannel.IsNsfw { get { throw new NotSupportedException(); } } IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { throw new NotSupportedException(); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs index d26c593ba..0c22a03f7 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcChannel.cs @@ -8,7 +8,6 @@ namespace Discord.Rpc public class RpcChannel : RpcEntity { public string Name { get; private set; } - public bool IsNsfw => ChannelHelper.IsNsfw(Name); public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index 72b45e466..9de2968db 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -16,6 +16,8 @@ namespace Discord.Rpc public IReadOnlyCollection CachedMessages { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); + // TODO: Check if RPC includes the 'nsfw' field on Channel models + public bool IsNsfw => ChannelHelper.IsNsfw(this); internal RpcTextChannel(DiscordRpcClient discord, ulong id, ulong guildId) : base(discord, id, guildId) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index b0d7261fd..502e61d15 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -41,7 +41,6 @@ namespace Discord.WebSocket //IChannel string IChannel.Name => null; - bool IChannel.IsNsfw => false; Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 1b351aae4..07ec630d3 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,7 +16,9 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } - internal bool Nsfw { get; private set; } + + private bool _nsfw; + public bool IsNsfw => _nsfw || ChannelHelper.IsNsfw(this); public string Mention => MentionUtils.MentionChannel(Id); public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); @@ -42,7 +44,7 @@ namespace Discord.WebSocket base.Update(state, model); Topic = model.Topic.Value; - Nsfw = model.Nsfw.GetValueOrDefault(); + _nsfw = model.Nsfw.GetValueOrDefault(); } public Task ModifyAsync(Action func, RequestOptions options = null) @@ -146,8 +148,5 @@ namespace Discord.WebSocket => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); - - // IChannel - bool IChannel.IsNsfw => Nsfw || ChannelHelper.IsNsfw(this); } } \ No newline at end of file From 484626407472427e4f2aad9cdab2dc75feb7a611 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 29 Aug 2017 17:42:00 -0400 Subject: [PATCH 29/33] Add IsNsfw to TextChannelProperties --- .../Entities/Channels/TextChannelProperties.cs | 4 ++++ src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs | 2 ++ src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index 2461a09f2..b7b568133 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -7,5 +7,9 @@ /// What the topic of the channel should be set to. /// public Optional Topic { get; set; } + /// + /// Should this channel be flagged as NSFW? + /// + public Optional IsNsfw { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 311336ec3..9cabc67c1 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -8,5 +8,7 @@ namespace Discord.API.Rest { [JsonProperty("topic")] public Optional Topic { get; set; } + [JsonProperty("nsfw")] + public Optional IsNsfw { get; set; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 30c28bacb..d61b5d14a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -41,7 +41,8 @@ namespace Discord.Rest { Name = args.Name, Position = args.Position, - Topic = args.Topic + Topic = args.Topic, + IsNsfw = args.IsNsfw }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } From 479361bbea4f7baebbb55ceb211ce9f02368b91e Mon Sep 17 00:00:00 2001 From: Mark Gross Date: Tue, 29 Aug 2017 16:38:34 -0700 Subject: [PATCH 30/33] Condense redundant AddField overloads in EmbedBuilder (#790) * Remove extra AddField overload in EmbedBuilder * Remove AddInlineField() --- .../Entities/Messages/EmbedBuilder.cs | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs index 7a111d34b..f5663cea3 100644 --- a/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Rest/Entities/Messages/EmbedBuilder.cs @@ -171,24 +171,16 @@ namespace Discord return this; } - public EmbedBuilder AddField(string name, object value) + public EmbedBuilder AddField(string name, object value, bool inline = false) { var field = new EmbedFieldBuilder() - .WithIsInline(false) - .WithName(name) - .WithValue(value); - AddField(field); - return this; - } - public EmbedBuilder AddInlineField(string name, object value) - { - var field = new EmbedFieldBuilder() - .WithIsInline(true) + .WithIsInline(inline) .WithName(name) .WithValue(value); AddField(field); return this; } + public EmbedBuilder AddField(EmbedFieldBuilder field) { if (Fields.Count >= MaxFieldCount) @@ -206,17 +198,6 @@ namespace Discord this.AddField(field); return this; } - public EmbedBuilder AddField(string title, string text, bool inline = false) - { - var field = new EmbedFieldBuilder - { - Name = title, - Value = text, - IsInline = inline - }; - _fields.Add(field); - return this; - } public Embed Build() { From ec03883e26fca5f67cc00b8cd2ba8cc5b8158b73 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Sat, 9 Sep 2017 10:56:03 -0400 Subject: [PATCH 31/33] Create unspecified channel object for unknown channel types (#811) * Partial fix of #810, addresses critical connection issues. * Implement fix for REST. * Implement fix on RestChannel. --- .../Entities/Channels/RestChannel.cs | 8 ++++---- .../Entities/Channels/RestGuildChannel.cs | 5 +++-- .../Entities/Channels/SocketGuildChannel.cs | 13 +++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index bc521784d..342e57717 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -6,7 +6,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - public abstract class RestChannel : RestEntity, IChannel, IUpdateable + public class RestChannel : RestEntity, IChannel, IUpdateable { public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -25,7 +25,7 @@ namespace Discord.Rest case ChannelType.Group: return CreatePrivate(discord, model) as RestChannel; default: - throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); + return new RestChannel(discord, model.Id); } } internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) @@ -40,9 +40,9 @@ namespace Discord.Rest throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } } - internal abstract void Update(Model model); + internal virtual void Update(Model model) { } - public abstract Task UpdateAsync(RequestOptions options = null); + public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); //IChannel string IChannel.Name => null; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 114c886c4..07832a3a9 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 abstract class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable + public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable { private ImmutableArray _overwrites; @@ -33,7 +33,8 @@ namespace Discord.Rest case ChannelType.Voice: return RestVoiceChannel.Create(discord, guild, model); default: - throw new InvalidOperationException("Unknown guild channel type"); + // TODO: Channel categories + return new RestGuildChannel(discord, guild, model.Id); } } internal override void Update(Model model) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index 0e7cfde82..1fe9a741f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.WebSocket { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public abstract class SocketGuildChannel : SocketChannel, IGuildChannel + public class SocketGuildChannel : SocketChannel, IGuildChannel { private ImmutableArray _overwrites; @@ -19,7 +19,7 @@ namespace Discord.WebSocket public int Position { get; private set; } public IReadOnlyCollection PermissionOverwrites => _overwrites; - public new abstract IReadOnlyCollection Users { get; } + public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id) @@ -35,7 +35,8 @@ namespace Discord.WebSocket case ChannelType.Voice: return SocketVoiceChannel.Create(guild, state, model); default: - throw new InvalidOperationException("Unknown guild channel type"); + // TODO: Proper implementation for channel categories + return new SocketGuildChannel(guild.Discord, model.Id, guild); } } internal override void Update(ClientState state, Model model) @@ -49,7 +50,7 @@ namespace Discord.WebSocket newOverwrites.Add(overwrites[i].ToEntity()); _overwrites = newOverwrites.ToImmutable(); } - + public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); public Task DeleteAsync(RequestOptions options = null) @@ -115,7 +116,7 @@ namespace Discord.WebSocket public async Task CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); - public new abstract SocketGuildUser GetUser(ulong id); + public new virtual SocketGuildUser GetUser(ulong id) => null; public override string ToString() => Name; internal new SocketGuildChannel Clone() => MemberwiseClone() as SocketGuildChannel; @@ -145,7 +146,7 @@ namespace Discord.WebSocket => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); - + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) From e25054bb3b0a756fef02ed44fa90d02e56865b1e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 11 Sep 2017 05:14:45 +0200 Subject: [PATCH 32/33] Update WS4Net dependency to 0.15.0 (#812) Also changes target to NetStandard1.3 --- .../Discord.Net.Providers.WS4Net.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj index 0115d91c0..78987e739 100644 --- a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj +++ b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj @@ -4,12 +4,12 @@ Discord.Net.Providers.WS4Net Discord.Providers.WS4Net An optional WebSocket client provider for Discord.Net using WebSocket4Net - net45 + netstandard1.3 - + - \ No newline at end of file + From 30e867a183d1f869570fdef3002dad359e93bdbe Mon Sep 17 00:00:00 2001 From: Chris Johnston <16418643+Chris-Johnston@users.noreply.github.com> Date: Sat, 23 Sep 2017 14:00:08 -0700 Subject: [PATCH 33/33] Implement welcome message channels (#819) --- .../Entities/Guilds/GuildProperties.cs | 8 ++++++++ .../Entities/Guilds/IGuild.cs | 3 +++ src/Discord.Net.Rest/API/Common/Guild.cs | 2 ++ .../API/Rest/ModifyGuildParams.cs | 2 ++ .../Entities/Guilds/GuildHelper.cs | 6 ++++++ .../Entities/Guilds/RestGuild.cs | 19 +++++++++++++++++++ .../Entities/Guilds/SocketGuild.cs | 13 +++++++++++++ 7 files changed, 53 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index ebec03e0b..1b406ef7f 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -60,6 +60,14 @@ /// public Optional AfkChannelId { get; set; } /// + /// The ITextChannel where System messages should be sent. + /// + public Optional SystemChannel { get; set; } + /// + /// The ID of the ITextChannel where System messages should be sent. + /// + public Optional SystemChannelId { get; set; } + /// /// The owner of this guild. /// public Optional Owner { get; set; } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 7874f5fd1..3ded9e038 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -36,6 +36,8 @@ namespace Discord ulong DefaultChannelId { get; } /// Gets the id of the embed channel for this guild if set, or null if not. ulong? EmbedChannelId { get; } + /// Gets the id of the channel where randomized welcome messages are sent, or null if not. + ulong? SystemChannelId { get; } /// Gets the id of the user that created this guild. ulong OwnerId { get; } /// Gets the id of the region hosting this guild's voice channels. @@ -84,6 +86,7 @@ namespace Discord Task> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Creates a new text channel. diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index b69ba1293..0ca1bc236 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -25,6 +25,8 @@ namespace Discord.API public bool EmbedEnabled { get; set; } [JsonProperty("embed_channel_id")] public ulong? EmbedChannelId { get; set; } + [JsonProperty("system_channel_id")] + public ulong? SystemChannelId { get; set; } [JsonProperty("verification_level")] public VerificationLevel VerificationLevel { get; set; } [JsonProperty("voice_states")] diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs index 2c7d84087..8de10f534 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs @@ -18,6 +18,8 @@ namespace Discord.API.Rest public Optional DefaultMessageNotifications { get; set; } [JsonProperty("afk_timeout")] public Optional AfkTimeout { get; set; } + [JsonProperty("system_channel_id")] + public Optional SystemChannelId { get; set; } [JsonProperty("icon")] public Optional Icon { get; set; } [JsonProperty("splash")] diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 5cfb1e566..2fa29928c 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -26,6 +26,7 @@ namespace Discord.Rest { AfkChannelId = args.AfkChannelId, AfkTimeout = args.AfkTimeout, + SystemChannelId = args.SystemChannelId, DefaultMessageNotifications = args.DefaultMessageNotifications, Icon = args.Icon.IsSpecified ? args.Icon.Value?.ToModel() : Optional.Create(), Name = args.Name, @@ -39,6 +40,11 @@ namespace Discord.Rest else if (args.AfkChannelId.IsSpecified) apiArgs.AfkChannelId = args.AfkChannelId.Value; + if (args.SystemChannel.IsSpecified) + apiArgs.SystemChannelId = args.SystemChannel.Value.Id; + else if (args.SystemChannelId.IsSpecified) + apiArgs.SystemChannelId = args.SystemChannelId.Value; + if (args.Owner.IsSpecified) apiArgs.OwnerId = args.Owner.Value.Id; else if (args.OwnerId.IsSpecified) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 0ad76c61a..aee305951 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -26,6 +26,7 @@ namespace Discord.Rest public ulong? AFKChannelId { get; private set; } public ulong? EmbedChannelId { get; private set; } + public ulong? SystemChannelId { get; private set; } public ulong OwnerId { get; private set; } public string VoiceRegionId { get; private set; } public string IconId { get; private set; } @@ -58,6 +59,7 @@ namespace Discord.Rest { AFKChannelId = model.AFKChannelId; EmbedChannelId = model.EmbedChannelId; + SystemChannelId = model.SystemChannelId; AFKTimeout = model.AFKTimeout; IsEmbeddable = model.EmbedEnabled; IconId = model.Icon; @@ -201,6 +203,16 @@ namespace Discord.Rest return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); return null; } + public async Task GetSystemChannelAsync(RequestOptions options = null) + { + var systemId = SystemChannelId; + if (systemId.HasValue) + { + var channel = await GuildHelper.GetChannelAsync(this, Discord, systemId.Value, options).ConfigureAwait(false); + return channel as RestTextChannel; + } + return null; + } public Task CreateTextChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options); public Task CreateVoiceChannelAsync(string name, RequestOptions options = null) @@ -320,6 +332,13 @@ namespace Discord.Rest else return null; } + async Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetSystemChannelAsync(options).ConfigureAwait(false); + 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) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e4e52bbff..b47ca84e8 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -47,6 +47,7 @@ namespace Discord.WebSocket internal ulong? AFKChannelId { get; private set; } internal ulong? EmbedChannelId { get; private set; } + internal ulong? SystemChannelId { get; private set; } public ulong OwnerId { get; private set; } public SocketGuildUser Owner => GetUser(OwnerId); public string VoiceRegionId { get; private set; } @@ -81,6 +82,14 @@ namespace Discord.WebSocket return id.HasValue ? GetChannel(id.Value) : null; } } + public SocketTextChannel SystemChannel + { + get + { + var id = SystemChannelId; + return id.HasValue ? GetTextChannel(id.Value) : null; + } + } public IReadOnlyCollection TextChannels => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); public IReadOnlyCollection VoiceChannels @@ -191,6 +200,7 @@ namespace Discord.WebSocket { AFKChannelId = model.AFKChannelId; EmbedChannelId = model.EmbedChannelId; + SystemChannelId = model.SystemChannelId; AFKTimeout = model.AFKTimeout; IsEmbeddable = model.EmbedEnabled; IconId = model.Icon; @@ -607,6 +617,7 @@ namespace Discord.WebSocket bool IGuild.Available => true; ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; ulong? IGuild.EmbedChannelId => EmbedChannelId; + ulong? IGuild.SystemChannelId => SystemChannelId; IRole IGuild.EveryoneRole => EveryoneRole; IReadOnlyCollection IGuild.Roles => Roles; @@ -631,6 +642,8 @@ namespace Discord.WebSocket => Task.FromResult(DefaultChannel); Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) => 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)