From f3cd96d2fec0819a60c551fce5f0f8bd4f3a11da Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 25 Oct 2016 21:37:18 +0000 Subject: [PATCH 01/88] Implemented IComparable on IRole and IGuildUUser --- src/Discord.Net.Core/Entities/Roles/IRole.cs | 4 ++-- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 2 +- .../Extensions/GuildUserExtensions.cs | 12 ++++++++++++ src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 6 +++--- src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 3 +++ .../Entities/Roles/SocketRole.cs | 1 + .../Entities/Users/SocketGuildUser.cs | 1 + 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index aa34fb019..2e3d63702 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Discord { - public interface IRole : ISnowflakeEntity, IDeletable, IMentionable + public interface IRole : ISnowflakeEntity, IDeletable, IMentionable, IComparable { /// Gets the guild owning this role. IGuild Guild { get; } @@ -27,4 +27,4 @@ namespace Discord ///// Modifies this role. Task ModifyAsync(Action func, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index b48c76a37..139640e5a 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord { /// A Guild-User pairing. - public interface IGuildUser : IUser, IVoiceState + public interface IGuildUser : IUser, IVoiceState, IComparable { /// Gets when this user joined this guild. DateTimeOffset? JoinedAt { get; } diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index 57d6a13dc..51e00fb42 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -15,5 +15,17 @@ namespace Discord => RemoveRolesAsync(user, (IEnumerable)roles); public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable roles) => user.ModifyAsync(x => x.RoleIds = user.RoleIds.Except(roles.Select(y => y.Id)).ToArray()); + + public static IEnumerable GetRoles(this IGuildUser user) { + var guild = user.Guild; + return user.RoleIds.Select(r => guild.GetRole(r)); + } + + internal static int Compare(this IGuildUser u1, IGuildUser u2) { + var r1 = u1.GetRoles().Max(); + var r2 = u2.GetRoles().Max(); + var result = r1.CompareTo(r2); + return result != 0 ? result : u1.Id.CompareTo(u2.Id); + } } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 6e81ce9df..60d868caa 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -4,9 +4,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.Role; -namespace Discord.Rest -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] +namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestRole : RestEntity, IRole { public RestGuild Guild { get; } @@ -51,10 +49,12 @@ namespace Discord.Rest public Task DeleteAsync(RequestOptions options = null) => RoleHelper.DeleteAsync(this, Discord, options); + public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id})"; //IRole IGuild IRole.Guild => Guild; + public int CompareTo(IRole role) => Position.CompareTo(role.Position); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 180ad38bc..6e242e264 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -96,6 +96,8 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } + + public int CompareTo(IGuildUser user) => this.Compare(user); //IVoiceState bool IVoiceState.IsSelfDeafened => false; @@ -103,5 +105,6 @@ namespace Discord.Rest bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + } } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index 515389da1..d6612dcb6 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -57,5 +57,6 @@ namespace Discord.WebSocket //IRole IGuild IRole.Guild => Guild; + public int CompareTo(IRole role) => Position.CompareTo(role.Position); } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 5a670c14d..3610b6284 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -96,6 +96,7 @@ namespace Discord.WebSocket IGuild IGuildUser.Guild => Guild; ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => RoleIds; + public int CompareTo(IGuildUser user) => this.Compare(user); //IUser Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) From 50d1aa068956f709ae190a036be44f8e8ffd4742 Mon Sep 17 00:00:00 2001 From: james7132 Date: Tue, 25 Oct 2016 21:43:41 +0000 Subject: [PATCH 02/88] Implemented IComparable on IGuildUser --- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 2 +- src/Discord.Net.Core/Extensions/GuildUserExtensions.cs | 5 +++++ src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 1 + src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 139640e5a..01ce78a24 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord { /// A Guild-User pairing. - public interface IGuildUser : IUser, IVoiceState, IComparable + public interface IGuildUser : IUser, IVoiceState, IComparable, IComparable { /// Gets when this user joined this guild. DateTimeOffset? JoinedAt { get; } diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index 51e00fb42..b66a75b1f 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -22,10 +22,15 @@ namespace Discord } internal static int Compare(this IGuildUser u1, IGuildUser u2) { + // These should never be empty since the everyone role is always present var r1 = u1.GetRoles().Max(); var r2 = u2.GetRoles().Max(); var result = r1.CompareTo(r2); return result != 0 ? result : u1.Id.CompareTo(u2.Id); } + + internal static int Compare(this IGuildUser user, IRole role) { + return user.GetRoles().Max().CompareTo(role); + } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 6e242e264..bebd245e6 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -98,6 +98,7 @@ namespace Discord.Rest } public int CompareTo(IGuildUser user) => this.Compare(user); + public int CompareTo(IRole role) => this.Compare(role); //IVoiceState bool IVoiceState.IsSelfDeafened => false; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 3610b6284..9c8165d30 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -97,6 +97,7 @@ namespace Discord.WebSocket ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => RoleIds; public int CompareTo(IGuildUser user) => this.Compare(user); + public int CompareTo(IRole role) => this.Compare(role); //IUser Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) From 08c7b49aae27098c84b361542748f49ac2b0962e Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 29 Oct 2016 07:50:26 +0000 Subject: [PATCH 03/88] Remove IComparable --- src/Discord.Net.Core/Entities/Users/IGuildUser.cs | 2 +- src/Discord.Net.Core/Extensions/GuildUserExtensions.cs | 4 ++-- src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs | 1 - src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 01ce78a24..3808bd435 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord { /// A Guild-User pairing. - public interface IGuildUser : IUser, IVoiceState, IComparable, IComparable + public interface IGuildUser : IUser, IVoiceState, IComparable { /// Gets when this user joined this guild. DateTimeOffset? JoinedAt { get; } diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index b66a75b1f..4067d0b3e 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -21,7 +21,7 @@ namespace Discord return user.RoleIds.Select(r => guild.GetRole(r)); } - internal static int Compare(this IGuildUser u1, IGuildUser u2) { + public static int CompareRoles(this IGuildUser u1, IGuildUser u2) { // These should never be empty since the everyone role is always present var r1 = u1.GetRoles().Max(); var r2 = u2.GetRoles().Max(); @@ -29,7 +29,7 @@ namespace Discord return result != 0 ? result : u1.Id.CompareTo(u2.Id); } - internal static int Compare(this IGuildUser user, IRole role) { + public static int Compare(this IGuildUser user, IRole role) { return user.GetRoles().Max().CompareTo(role); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index bebd245e6..154307ace 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -97,7 +97,6 @@ namespace Discord.Rest } } - public int CompareTo(IGuildUser user) => this.Compare(user); public int CompareTo(IRole role) => this.Compare(role); //IVoiceState diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 9c8165d30..05ca7aabc 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -96,7 +96,6 @@ namespace Discord.WebSocket IGuild IGuildUser.Guild => Guild; ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => RoleIds; - public int CompareTo(IGuildUser user) => this.Compare(user); public int CompareTo(IRole role) => this.Compare(role); //IUser From 2e9bca5b85f8547844f21548cb4b348ced8181df Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 29 Oct 2016 08:03:58 +0000 Subject: [PATCH 04/88] Fix role comparison --- .../Extensions/RoleExtensions.cs | 19 +++++++++++++++++++ .../Entities/Roles/RestRole.cs | 2 +- .../Entities/Roles/SocketRole.cs | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Core/Extensions/RoleExtensions.cs diff --git a/src/Discord.Net.Core/Extensions/RoleExtensions.cs b/src/Discord.Net.Core/Extensions/RoleExtensions.cs new file mode 100644 index 000000000..db146a36c --- /dev/null +++ b/src/Discord.Net.Core/Extensions/RoleExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord +{ + public static class RoleExtensions + { + internal static int Compare(this IRole r1, IRole r2) { + if(r2 == null) + return 1; + var result = r1.Position.CompareTo(r2.Position); + // As per Discord's documentation, a tie is broken by ID + if(result != 0) + return result; + return r1.Id.CompareTo(r2.Id); + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 60d868caa..bfa081def 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -55,6 +55,6 @@ namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] //IRole IGuild IRole.Guild => Guild; - public int CompareTo(IRole role) => Position.CompareTo(role.Position); + public int CompareTo(IRole role) => this.Compare(role); } } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index d6612dcb6..5fa585138 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -57,6 +57,6 @@ namespace Discord.WebSocket //IRole IGuild IRole.Guild => Guild; - public int CompareTo(IRole role) => Position.CompareTo(role.Position); + public int CompareTo(IRole role) => this.CompareTo(role); } } From 89e61fe7d7b0c3447c8ba87429c7f377756bc67a Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 29 Oct 2016 20:36:56 +0000 Subject: [PATCH 05/88] Fixed variable naming --- src/Discord.Net.Core/Extensions/GuildUserExtensions.cs | 9 ++++----- src/Discord.Net.Core/Extensions/RoleExtensions.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index 4067d0b3e..ee53fceb8 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -21,12 +21,11 @@ namespace Discord return user.RoleIds.Select(r => guild.GetRole(r)); } - public static int CompareRoles(this IGuildUser u1, IGuildUser u2) { + public static int CompareRoles(this IGuildUser left, IGuildUser right) { // These should never be empty since the everyone role is always present - var r1 = u1.GetRoles().Max(); - var r2 = u2.GetRoles().Max(); - var result = r1.CompareTo(r2); - return result != 0 ? result : u1.Id.CompareTo(u2.Id); + var roleLeft = left.GetRoles().Max(); + var roleRight= right.GetRoles().Max(); + return roleLeft.CompareTo(roleRight); } public static int Compare(this IGuildUser user, IRole role) { diff --git a/src/Discord.Net.Core/Extensions/RoleExtensions.cs b/src/Discord.Net.Core/Extensions/RoleExtensions.cs index db146a36c..ae2d1ccce 100644 --- a/src/Discord.Net.Core/Extensions/RoleExtensions.cs +++ b/src/Discord.Net.Core/Extensions/RoleExtensions.cs @@ -6,14 +6,16 @@ namespace Discord { public static class RoleExtensions { - internal static int Compare(this IRole r1, IRole r2) { - if(r2 == null) + internal static int Compare(this IRole left, IRole right) { + if(left == null) + return -1; + if(right == null) return 1; - var result = r1.Position.CompareTo(r2.Position); + var result = left.Position.CompareTo(right.Position); // As per Discord's documentation, a tie is broken by ID if(result != 0) return result; - return r1.Id.CompareTo(r2.Id); + return left.Id.CompareTo(right.Id); } } } From 97a6bf6b6edb7eefe14040fba159f8d68b3bc5c8 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 29 Oct 2016 23:05:56 +0000 Subject: [PATCH 06/88] Add IGuildUser.Hirearchy, added docstrings Property can be renamed as needed. --- .../Entities/Users/IGuildUser.cs | 5 +++++ .../Extensions/GuildUserExtensions.cs | 19 ++++++++++++------- .../Entities/Users/RestGuildUser.cs | 4 ++-- .../Entities/Users/SocketGuildUser.cs | 3 ++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 3808bd435..d944104a2 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -28,5 +28,10 @@ namespace Discord Task KickAsync(RequestOptions options = null); /// Modifies this user's properties in this guild. Task ModifyAsync(Action func, RequestOptions options = null); + + /// The position of the user within the role hirearchy. + /// The returned value equal to the position of the highest role the user has, + /// or int.MaxValue if user is the server owner. + int Hirearchy { get; } } } diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index ee53fceb8..181442150 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -21,15 +21,20 @@ namespace Discord return user.RoleIds.Select(r => guild.GetRole(r)); } - public static int CompareRoles(this IGuildUser left, IGuildUser right) { - // These should never be empty since the everyone role is always present - var roleLeft = left.GetRoles().Max(); - var roleRight= right.GetRoles().Max(); - return roleLeft.CompareTo(roleRight); + internal static int GetHirearchy(this IGuildUser user) { + if(user == null) + return -1; + if(user.Id == user.Guild.OwnerId) + return int.MaxValue; + return user.GetRoles().Max(r => r.Position); } - public static int Compare(this IGuildUser user, IRole role) { - return user.GetRoles().Max().CompareTo(role); + internal static int CompareRole(this IGuildUser user, IRole role) { + if(user == null) + return -1; + if(role == null) + return 1; + return -user.Hirearchy.CompareTo(role.Position); } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index 154307ace..227699bbd 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -96,8 +96,8 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); } } - - public int CompareTo(IRole role) => this.Compare(role); + public int Hirearchy => this.GetHirearchy(); + public int CompareTo(IRole role) => this.CompareRole(role); //IVoiceState bool IVoiceState.IsSelfDeafened => false; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 05ca7aabc..b04a0471b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -96,7 +96,8 @@ namespace Discord.WebSocket IGuild IGuildUser.Guild => Guild; ulong IGuildUser.GuildId => Guild.Id; IReadOnlyCollection IGuildUser.RoleIds => RoleIds; - public int CompareTo(IRole role) => this.Compare(role); + public int CompareTo(IRole role) => this.CompareRole(role); + public int Hirearchy => this.GetHirearchy(); //IUser Task IUser.GetDMChannelAsync(CacheMode mode, RequestOptions options) From 1d20029c7b022ac98610b913278fadc1ae2888b2 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Wed, 2 Nov 2016 03:01:42 -0400 Subject: [PATCH 07/88] Enumerate over Enum.GetNames to ensure equal value names are not excluded. --- src/Discord.Net.Commands/Readers/EnumTypeReader.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index dca845704..4d4c9260b 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -32,10 +32,12 @@ namespace Discord.Commands var byNameBuilder = ImmutableDictionary.CreateBuilder(); var byValueBuilder = ImmutableDictionary.CreateBuilder(); - foreach (var v in Enum.GetValues(_enumType)) + foreach (var v in Enum.GetNames(_enumType)) { - byNameBuilder.Add(v.ToString().ToLower(), v); - byValueBuilder.Add((T)v, v); + byNameBuilder.Add(v.ToLower(), v); + var parsedValue = (T)Enum.Parse(_enumType, v); + if (!byValueBuilder.ContainsKey(parsedValue)) + byValueBuilder.Add(parsedValue, v); } _enumsByName = byNameBuilder.ToImmutable(); @@ -59,7 +61,7 @@ namespace Discord.Commands if (_enumsByName.TryGetValue(input.ToLower(), out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Name is not a {_enumType.Name}")); } } } From bb9c43b21ada1955cdbc67f07c04c20804e85f4e Mon Sep 17 00:00:00 2001 From: ObsidianMinor Date: Mon, 7 Nov 2016 13:42:25 -0600 Subject: [PATCH 08/88] Update RequestQueueBucket.cs Access "Date" in response header safely with TryGetValue. Hopefully nothing bad happens when lag is 0 --- src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index b750afbf6..755c4e5ca 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -49,7 +49,9 @@ namespace Discord.Net.Queue Debug.WriteLine($"[{id}] Sending..."); var response = await request.SendAsync().ConfigureAwait(false); - TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); + string headerDate; + bool headerHasDate = response.Headers.TryGetValue("Date", out headerDate); + TimeSpan lag = DateTimeOffset.UtcNow - (headerHasDate ? DateTimeOffset.Parse(headerDate) : DateTimeOffset.UtcNow); var info = new RateLimitInfo(response.Headers); if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) @@ -236,4 +238,4 @@ namespace Discord.Net.Queue } } } -} \ No newline at end of file +} From a6f89b7c368203e47f8f19039660fb5e64a9b10d Mon Sep 17 00:00:00 2001 From: Sindre Date: Thu, 10 Nov 2016 16:47:51 +0100 Subject: [PATCH 09/88] Solves Issue 342, but there might be a cleaner way to do this that doesnt make you end up with a IGuild in the RestRole. --- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 2 +- src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs | 2 +- src/Discord.Net.Rest/Entities/Roles/RestRole.cs | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index f91f131d9..e920145a3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -142,7 +142,7 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); - var role = RestRole.Create(client, model); + var role = RestRole.Create(guild, client, model); await role.ModifyAsync(x => { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index a0ba7a6de..4bb0ab60d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -87,7 +87,7 @@ namespace Discord.Rest if (model.Roles != null) { for (int i = 0; i < model.Roles.Length; i++) - roles[model.Roles[i].Id] = RestRole.Create(Discord, model.Roles[i]); + roles[model.Roles[i].Id] = RestRole.Create(this, Discord, model.Roles[i]); } _roles = roles.ToImmutable(); diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 6e81ce9df..fcae5a21c 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -9,7 +9,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestRole : RestEntity, IRole { - public RestGuild Guild { get; } + public IGuild Guild { get; } public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } @@ -22,13 +22,14 @@ namespace Discord.Rest public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); - internal RestRole(BaseDiscordClient discord, ulong id) + internal RestRole(IGuild guild, BaseDiscordClient discord, ulong id) : base(discord, id) { + Guild = guild; } - internal static RestRole Create(BaseDiscordClient discord, Model model) + internal static RestRole Create(IGuild guild, BaseDiscordClient discord, Model model) { - var entity = new RestRole(discord, model.Id); + var entity = new RestRole(guild, discord, model.Id); entity.Update(model); return entity; } From 29c9ac9ef3cba5ca8af90cd1fde78686cd32be8b Mon Sep 17 00:00:00 2001 From: ObsidianMinor Date: Thu, 10 Nov 2016 21:21:21 -0600 Subject: [PATCH 10/88] Reverted old code and added simple IgnoreCase to header dictionary --- src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs | 4 +--- src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 755c4e5ca..6933730ff 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -49,9 +49,7 @@ namespace Discord.Net.Queue Debug.WriteLine($"[{id}] Sending..."); var response = await request.SendAsync().ConfigureAwait(false); - string headerDate; - bool headerHasDate = response.Headers.TryGetValue("Date", out headerDate); - TimeSpan lag = DateTimeOffset.UtcNow - (headerHasDate ? DateTimeOffset.Parse(headerDate) : DateTimeOffset.UtcNow); + TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); var info = new RateLimitInfo(response.Headers); if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) diff --git a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs index 5ec30c750..588785230 100644 --- a/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/DefaultRestClient.cs @@ -120,7 +120,7 @@ namespace Discord.Net.Rest cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); - var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault()); + var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; return new RestResponse(response.StatusCode, headers, stream); From c037865b28f3e5c8f3df75f1c3edb77e4a27610f Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 12 Nov 2016 01:00:42 -0400 Subject: [PATCH 11/88] Cleaned up PR --- .../Entities/Guilds/GuildHelper.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 2 +- .../Entities/Roles/RestRole.cs | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index e920145a3..7aaa19304 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -142,7 +142,7 @@ namespace Discord.Rest if (name == null) throw new ArgumentNullException(nameof(name)); var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); - var role = RestRole.Create(guild, client, model); + var role = RestRole.Create(client, guild, model); await role.ModifyAsync(x => { diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 4bb0ab60d..a85107831 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -87,7 +87,7 @@ namespace Discord.Rest if (model.Roles != null) { for (int i = 0; i < model.Roles.Length; i++) - roles[model.Roles[i].Id] = RestRole.Create(this, Discord, model.Roles[i]); + roles[model.Roles[i].Id] = RestRole.Create(Discord, this, model.Roles[i]); } _roles = roles.ToImmutable(); diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index fcae5a21c..cc42fc57d 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -9,7 +9,7 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestRole : RestEntity, IRole { - public IGuild Guild { get; } + internal IGuild Guild { get; } public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } @@ -22,14 +22,14 @@ namespace Discord.Rest public bool IsEveryone => Id == Guild.Id; public string Mention => MentionUtils.MentionRole(Id); - internal RestRole(IGuild guild, BaseDiscordClient discord, ulong id) + internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, id) { Guild = guild; } - internal static RestRole Create(IGuild guild, BaseDiscordClient discord, Model model) + internal static RestRole Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestRole(guild, discord, model.Id); + var entity = new RestRole(discord, guild, model.Id); entity.Update(model); return entity; } @@ -56,6 +56,14 @@ namespace Discord.Rest private string DebuggerDisplay => $"{Name} ({Id})"; //IRole - IGuild IRole.Guild => Guild; + IGuild IRole.Guild + { + get + { + if (Guild != null) + return Guild; + throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + } + } } } From d8440b764a6e6ee512934d18bde3ee7cf97da2cf Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 12 Nov 2016 00:07:25 -0500 Subject: [PATCH 12/88] Changes requested from review --- src/Discord.Net.Commands/Readers/EnumTypeReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index 4d4c9260b..cd8b73896 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -61,7 +61,7 @@ namespace Discord.Commands if (_enumsByName.TryGetValue(input.ToLower(), out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Name is not a {_enumType.Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); } } } From 6e8d1118ec0c2e47e2ef7a1e694a37dc046e1fba Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 12 Nov 2016 23:04:08 -0500 Subject: [PATCH 13/88] Update API models to fully support rich embeds --- src/Discord.Net.Core/API/Common/Embed.cs | 8 ++++++++ src/Discord.Net.Core/API/Common/EmbedAuthor.cs | 16 ++++++++++++++++ src/Discord.Net.Core/API/Common/EmbedField.cs | 14 ++++++++++++++ src/Discord.Net.Core/API/Common/EmbedFooter.cs | 14 ++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 src/Discord.Net.Core/API/Common/EmbedAuthor.cs create mode 100644 src/Discord.Net.Core/API/Common/EmbedField.cs create mode 100644 src/Discord.Net.Core/API/Common/EmbedFooter.cs diff --git a/src/Discord.Net.Core/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs index 9cea24313..b19281d24 100644 --- a/src/Discord.Net.Core/API/Common/Embed.cs +++ b/src/Discord.Net.Core/API/Common/Embed.cs @@ -13,9 +13,17 @@ namespace Discord.API public string Description { get; set; } [JsonProperty("url")] public string Url { get; set; } + [JsonProperty("color")] + public uint Color { get; set; } + [JsonProperty("author")] + public Optional Author { get; set; } + [JsonProperty("footer")] + public Optional Footer { get; set; } [JsonProperty("thumbnail")] public Optional Thumbnail { get; set; } [JsonProperty("provider")] public Optional Provider { get; set; } + [JsonProperty("fields")] + public Optional Fields { get; set; } } } diff --git a/src/Discord.Net.Core/API/Common/EmbedAuthor.cs b/src/Discord.Net.Core/API/Common/EmbedAuthor.cs new file mode 100644 index 000000000..973f7d5ea --- /dev/null +++ b/src/Discord.Net.Core/API/Common/EmbedAuthor.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class EmbedAuthor + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("icon_url")] + public string IconUrl { get; set; } + [JsonProperty("proxy_icon_url")] + public string ProxyIconUrl { get; set; } + } +} diff --git a/src/Discord.Net.Core/API/Common/EmbedField.cs b/src/Discord.Net.Core/API/Common/EmbedField.cs new file mode 100644 index 000000000..12aa0137a --- /dev/null +++ b/src/Discord.Net.Core/API/Common/EmbedField.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class EmbedField + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("value")] + public string Value { get; set; } + [JsonProperty("inline")] + public bool Inline { get; set; } + } +} diff --git a/src/Discord.Net.Core/API/Common/EmbedFooter.cs b/src/Discord.Net.Core/API/Common/EmbedFooter.cs new file mode 100644 index 000000000..2ad22cae7 --- /dev/null +++ b/src/Discord.Net.Core/API/Common/EmbedFooter.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class EmbedFooter + { + [JsonProperty("text")] + public string Text { get; set; } + [JsonProperty("icon_url")] + public string IconUrl { get; set; } + [JsonProperty("proxy_icon_url")] + public string ProxyIconUrl { get; set; } + } +} From 63b06ff4776dc10d0c3f9f9f63aa1a2c9c91d3ad Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 12 Nov 2016 23:23:38 -0500 Subject: [PATCH 14/88] Support Rich Embeds on Entities --- src/Discord.Net.Core/API/Common/Embed.cs | 2 +- .../Entities/Messages/EmbedAuthor.cs | 29 +++++++++++++++++++ .../Entities/Messages/EmbedField.cs | 27 +++++++++++++++++ .../Entities/Messages/EmbedFooter.cs | 27 +++++++++++++++++ .../Entities/Messages/IEmbed.cs | 8 ++++- .../Entities/Messages/Embed.cs | 26 ++++++++++++++--- 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedField.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs diff --git a/src/Discord.Net.Core/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs index b19281d24..ed4715237 100644 --- a/src/Discord.Net.Core/API/Common/Embed.cs +++ b/src/Discord.Net.Core/API/Common/Embed.cs @@ -14,7 +14,7 @@ namespace Discord.API [JsonProperty("url")] public string Url { get; set; } [JsonProperty("color")] - public uint Color { get; set; } + public uint? Color { get; set; } [JsonProperty("author")] public Optional Author { get; set; } [JsonProperty("footer")] diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs new file mode 100644 index 000000000..b0ed0f08f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using Model = Discord.API.EmbedAuthor; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public struct EmbedAuthor + { + public string Name { get; set; } + public string Url { get; set; } + public string IconUrl { get; set; } + public string ProxyIconUrl { get; set; } + + private EmbedAuthor(string name, string url, string iconUrl, string proxyIconUrl) + { + Name = name; + Url = url; + IconUrl = iconUrl; + ProxyIconUrl = proxyIconUrl; + } + internal static EmbedAuthor Create(Model model) + { + return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); + } + + private string DebuggerDisplay => $"{Name} ({Url})"; + public override string ToString() => Name; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs new file mode 100644 index 000000000..257074e41 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Model = Discord.API.EmbedField; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public struct EmbedField + { + public string Name { get; set; } + public string Value { get; set; } + public bool Inline { get; set; } + + private EmbedField(string name, string value, bool inline) + { + Name = name; + Value = value; + Inline = inline; + } + internal static EmbedField Create(Model model) + { + return new EmbedField(model.Name, model.Value, model.Inline); + } + + private string DebuggerDisplay => $"{Name} ({Value}"; + public override string ToString() => Name; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs new file mode 100644 index 000000000..a69e4b077 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Model = Discord.API.EmbedFooter; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public struct EmbedFooter + { + public string Text { get; set; } + public string IconUrl { get; set; } + public string ProxyUrl { get; set; } + + private EmbedFooter(string text, string iconUrl, string proxyUrl) + { + Text = text; + IconUrl = iconUrl; + ProxyUrl = proxyUrl; + } + internal static EmbedFooter Create(Model model) + { + return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl); + } + + private string DebuggerDisplay => $"{Text} ({IconUrl})"; + public override string ToString() => Text; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 3bca85fd0..311fbff01 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -1,4 +1,6 @@ -namespace Discord +using System.Collections.Immutable; + +namespace Discord { public interface IEmbed { @@ -6,7 +8,11 @@ string Type { get; } string Title { get; } string Description { get; } + uint? Color { get; } + EmbedAuthor? Author { get; } + EmbedFooter? Footer { get; } EmbedProvider? Provider { get; } EmbedThumbnail? Thumbnail { get; } + ImmutableArray Fields { get; } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/Embed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs index 20979534e..e85bf9cca 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; using Model = Discord.API.Embed; namespace Discord @@ -10,10 +12,23 @@ namespace Discord public string Url { get; } public string Title { get; } public string Type { get; } + public uint? Color { get; } + public EmbedAuthor? Author { get; } + public EmbedFooter? Footer { get; } public EmbedProvider? Provider { get; } public EmbedThumbnail? Thumbnail { get; } + public ImmutableArray Fields { get; } - internal Embed(string type, string title, string description, string url, EmbedProvider? provider, EmbedThumbnail? thumbnail) + internal Embed(string type, + string title, + string description, + string url, + uint? color, + EmbedAuthor? author, + EmbedFooter? footer, + EmbedProvider? provider, + EmbedThumbnail? thumbnail, + ImmutableArray fields) { Type = type; Title = title; @@ -24,9 +39,12 @@ namespace Discord } internal static Embed Create(Model model) { - return new Embed(model.Type, model.Title, model.Description, model.Url, + return new Embed(model.Type, model.Title, model.Description, model.Url, model.Color, + model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, + model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, - model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null); + model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null, + model.Fields.IsSpecified ? model.Fields.Value.Select(x => EmbedField.Create(x)).ToImmutableArray() : ImmutableArray.Create()); } public override string ToString() => Title; From 52f979ec8a19600872c0bb73b4f7c27f1d2b6027 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 13 Nov 2016 00:35:46 -0500 Subject: [PATCH 15/88] Support sending rich embeds, add an Embed Builder --- .../API/Rest/CreateMessageParams.cs | 3 + .../Entities/Messages/IEmbed.cs | 2 +- src/Discord.Net.Core/Utils/EmbedBuilder.cs | 132 ++++++++++++++++++ .../Entities/Channels/ChannelHelper.cs | 7 + .../Entities/Messages/Embed.cs | 14 +- 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 src/Discord.Net.Core/Utils/EmbedBuilder.cs diff --git a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs index 9577ab579..a0dbb59dd 100644 --- a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API.Rest @@ -13,6 +14,8 @@ namespace Discord.API.Rest public Optional Nonce { get; set; } [JsonProperty("tts")] public Optional IsTTS { get; set; } + [JsonProperty("embed")] + public Optional Embed { get; set; } public CreateMessageParams(string content) { diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 311fbff01..c46b22d17 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -8,7 +8,7 @@ namespace Discord string Type { get; } string Title { get; } string Description { get; } - uint? Color { get; } + Color? Color { get; } EmbedAuthor? Author { get; } EmbedFooter? Footer { get; } EmbedProvider? Provider { get; } diff --git a/src/Discord.Net.Core/Utils/EmbedBuilder.cs b/src/Discord.Net.Core/Utils/EmbedBuilder.cs new file mode 100644 index 000000000..42861709f --- /dev/null +++ b/src/Discord.Net.Core/Utils/EmbedBuilder.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Embed = Discord.API.Embed; +using Field = Discord.API.EmbedField; +using Author = Discord.API.EmbedAuthor; +using Footer = Discord.API.EmbedFooter; + +namespace Discord +{ + public class EmbedBuilder + { + private Embed embed = new Embed(); + List fields = new List(); + + public EmbedBuilder() + { + embed.Type = "rich"; + } + + public EmbedBuilder Title(string title) + { + embed.Title = title; + return this; + } + public EmbedBuilder Description(string description) + { + embed.Description = description; + return this; + } + public EmbedBuilder Url(string url) + { + embed.Url = url; + return this; + } + public EmbedBuilder Color(Color color) + { + embed.Color = color.RawValue; + return this; + } + public EmbedBuilder Field(Func builder) + { + fields.Add(builder(new EmbedFieldBuilder()).Build()); + return this; + } + public EmbedBuilder Author(Func builder) + { + embed.Author = builder(new EmbedAuthorBuilder()).Build(); + return this; + } + public EmbedBuilder Footer(Func builder) + { + embed.Footer = builder(new EmbedFooterBuilder()).Build(); + return this; + } + public Embed Build() + { + embed.Fields = fields.ToArray(); + return embed; + } + + } + + public class EmbedFieldBuilder + { + private Field embedField = new Field(); + + public EmbedFieldBuilder Name(string name) + { + embedField.Name = name; + return this; + } + public EmbedFieldBuilder Value(string value) + { + embedField.Value = value; + return this; + } + public EmbedFieldBuilder Inline(bool inline) + { + embedField.Inline = inline; + return this; + } + public Field Build() + { + return embedField; + } + } + + public class EmbedAuthorBuilder + { + private Author author = new Author(); + + public EmbedAuthorBuilder Name(string name) + { + author.Name = name; + return this; + } + public EmbedAuthorBuilder Url(string url) + { + author.Url = url; + return this; + } + public EmbedAuthorBuilder IconUrl(string iconUrl) + { + author.IconUrl = iconUrl; + return this; + } + public Author Build() + { + return author; + } + } + + public class EmbedFooterBuilder + { + private Footer footer = new Footer(); + + public EmbedFooterBuilder Text(string text) + { + footer.Text = text; + return this; + } + public EmbedFooterBuilder IconUrl(string iconUrl) + { + footer.IconUrl = iconUrl; + return this; + } + public Footer Build() + { + return footer; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 858269366..a27bf0493 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -116,6 +116,13 @@ namespace Discord.Rest var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, guild, model); } + public static async Task SendRichMessageAsync(IChannel channel, BaseDiscordClient client, + string text, bool isTTS, API.Embed embed, IGuild guild, RequestOptions options) + { + var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed }; + var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); + return RestUserMessage.Create(client, guild, model); + } public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS, IGuild guild, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Messages/Embed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs index e85bf9cca..540a39ea2 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Model = Discord.API.Embed; @@ -12,7 +13,7 @@ namespace Discord public string Url { get; } public string Title { get; } public string Type { get; } - public uint? Color { get; } + public Color? Color { get; } public EmbedAuthor? Author { get; } public EmbedFooter? Footer { get; } public EmbedProvider? Provider { get; } @@ -23,7 +24,7 @@ namespace Discord string title, string description, string url, - uint? color, + Color? color, EmbedAuthor? author, EmbedFooter? footer, EmbedProvider? provider, @@ -34,12 +35,17 @@ namespace Discord Title = title; Description = description; Url = url; + Color = color; + Author = author; + Footer = footer; Provider = provider; Thumbnail = thumbnail; + Fields = fields; } internal static Embed Create(Model model) { - return new Embed(model.Type, model.Title, model.Description, model.Url, model.Color, + return new Embed(model.Type, model.Title, model.Description, model.Url, + model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, From bad7d827c3a7ac98a07cca9f20afa422242bdc04 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 13 Nov 2016 00:51:40 -0500 Subject: [PATCH 16/88] Modify SendMessage to include an embeds field --- src/Discord.Net.Commands/ModuleBase.cs | 4 ++-- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 7 ------- .../Entities/Channels/IRestMessageChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 8 ++++---- .../Entities/Channels/RestGroupChannel.cs | 8 ++++---- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 8 ++++---- .../Entities/Channels/RestVirtualMessageChannel.cs | 8 ++++---- src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs | 8 ++++---- src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs | 8 ++++---- src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs | 8 ++++---- .../Entities/Channels/ISocketMessageChannel.cs | 2 +- .../Entities/Channels/SocketDMChannel.cs | 8 ++++---- .../Entities/Channels/SocketGroupChannel.cs | 8 ++++---- .../Entities/Channels/SocketTextChannel.cs | 8 ++++---- 15 files changed, 45 insertions(+), 52 deletions(-) diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index c5b6d7436..73770402e 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -6,9 +6,9 @@ namespace Discord.Commands { public CommandContext Context { get; internal set; } - protected virtual async Task ReplyAsync(string message, bool isTTS = false, RequestOptions options = null) + protected virtual async Task ReplyAsync(string message, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) { - return await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false); + return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 7c13e4a6f..30ca044bd 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -8,7 +8,7 @@ namespace Discord public interface IMessageChannel : IChannel { /// Sends a message to this message channel. - Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); + Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index a27bf0493..aecdf292e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -110,13 +110,6 @@ namespace Discord.Rest } public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, - string text, bool isTTS, IGuild guild, RequestOptions options) - { - var args = new CreateMessageParams(text) { IsTTS = isTTS }; - var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); - return RestUserMessage.Create(client, guild, model); - } - public static async Task SendRichMessageAsync(IChannel channel, BaseDiscordClient client, string text, bool isTTS, API.Embed embed, IGuild guild, RequestOptions options) { var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed }; diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 6d2b729dd..82783700e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -7,7 +7,7 @@ namespace Discord.Rest public interface IRestMessageChannel : IMessageChannel { /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); + new Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 9d866220d..8c547ad80 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -63,8 +63,8 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -126,8 +126,8 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index bb840ece4..eb29192c4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -76,8 +76,8 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -136,8 +136,8 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 4a617517e..5c6fc371b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -55,8 +55,8 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -108,8 +108,8 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs index 97a0a93e6..a99430a82 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -33,8 +33,8 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) @@ -86,8 +86,8 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index 3df04b320..e9393b5f9 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -44,8 +44,8 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -104,8 +104,8 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index d88847067..12fb91343 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -46,8 +46,8 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -103,8 +103,8 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index e6e3d3e48..7b20660ab 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -49,8 +49,8 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -105,8 +105,8 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 7ba08544b..ed22c78a8 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null); + new Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 4da4139d0..e9d37cc9c 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -66,8 +66,8 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -134,8 +134,8 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 3b47a8452..723df5390 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -89,8 +89,8 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -197,8 +197,8 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 8dff22232..693af4312 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -72,8 +72,8 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, Guild, options); - public Task SendMessageAsync(string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, Guild, options); + public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, Guild, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, Guild, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) @@ -135,8 +135,8 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, RequestOptions options) - => await SendMessageAsync(text, isTTS, options).ConfigureAwait(false); + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); } From 8866a1499c33bf1bc585f1b95033379a7c03fef6 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 13 Nov 2016 01:02:44 -0500 Subject: [PATCH 17/88] Allow content to be empty when sending a message --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 3 +-- src/Discord.Net.Core/API/Rest/CreateMessageParams.cs | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index ac18e8ace..91bdbfd11 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -439,8 +439,7 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - if (args.Content.Length > DiscordConfig.MaxMessageSize) + if (args.Content.GetValueOrDefault()?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs index a0dbb59dd..a6b06f9f8 100644 --- a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs @@ -8,7 +8,7 @@ namespace Discord.API.Rest public class CreateMessageParams { [JsonProperty("content")] - public string Content { get; } + public Optional Content { get; } [JsonProperty("nonce")] public Optional Nonce { get; set; } @@ -19,7 +19,10 @@ namespace Discord.API.Rest public CreateMessageParams(string content) { - Content = content; + if (string.IsNullOrEmpty(content)) + Content = null; + else + Content = content; } } } From 754970bb566efc55ea51d2c4380bbfe46cb7cf90 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 13 Nov 2016 13:54:46 -0500 Subject: [PATCH 18/88] When sending a message with no content, do not send a null value. Resolves an issue where the ratelimiter would panic. --- src/Discord.Net.Core/API/Rest/CreateMessageParams.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs index a6b06f9f8..9904f12bb 100644 --- a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs @@ -9,7 +9,6 @@ namespace Discord.API.Rest { [JsonProperty("content")] public Optional Content { get; } - [JsonProperty("nonce")] public Optional Nonce { get; set; } [JsonProperty("tts")] @@ -20,7 +19,7 @@ namespace Discord.API.Rest public CreateMessageParams(string content) { if (string.IsNullOrEmpty(content)) - Content = null; + Content = Optional.Create(); else Content = content; } From 3698dbfedc209bb2e962bedfd59185c754c0b87b Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 13 Nov 2016 14:16:49 -0500 Subject: [PATCH 19/88] Only allow messages with an embed present to be sent with no content --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 5 ++++- src/Discord.Net.Core/API/Rest/CreateMessageParams.cs | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 91bdbfd11..7268d7b34 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -439,7 +439,10 @@ namespace Discord.API { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); - if (args.Content.GetValueOrDefault()?.Length > DiscordConfig.MaxMessageSize) + if (!args.Embed.IsSpecified || args.Embed.Value == null) + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + + if (args.Content.Length > DiscordConfig.MaxMessageSize) throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs index 9904f12bb..a0dbb59dd 100644 --- a/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/CreateMessageParams.cs @@ -8,7 +8,8 @@ namespace Discord.API.Rest public class CreateMessageParams { [JsonProperty("content")] - public Optional Content { get; } + public string Content { get; } + [JsonProperty("nonce")] public Optional Nonce { get; set; } [JsonProperty("tts")] @@ -18,10 +19,7 @@ namespace Discord.API.Rest public CreateMessageParams(string content) { - if (string.IsNullOrEmpty(content)) - Content = Optional.Create(); - else - Content = content; + Content = content; } } } From 7773e1d1cb16acf31c1f10e9d2d70d4e77dddc70 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 07:56:44 -0400 Subject: [PATCH 20/88] Removed old GetBucketId function --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index ac18e8ace..966cdd83a 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -1059,19 +1059,6 @@ namespace Discord.API using (JsonReader reader = new JsonTextReader(text)) return _serializer.Deserialize(reader); } - internal string GetBucketId(ulong guildId = 0, ulong channelId = 0, [CallerMemberName] string methodName = "") - { - if (guildId != 0) - { - if (channelId != 0) - return $"{methodName}({guildId}/{channelId})"; - else - return $"{methodName}({guildId})"; - } - else if (channelId != 0) - return $"{methodName}({channelId})"; - return $"{methodName}()"; - } internal class BucketIds { From 2e95e4232f1f04b13fd87ad6b34520e308892e84 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 07:57:10 -0400 Subject: [PATCH 21/88] Disabled rate limit debugging by default --- .../Net/Queue/RequestQueue.cs | 2 ++ .../Net/Queue/RequestQueueBucket.cs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs index ab20d7c18..1ea586481 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueue.cs @@ -79,7 +79,9 @@ namespace Discord.Net.Queue int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); if (millis > 0) { +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive) [Global]"); +#endif await Task.Delay(millis).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 6933730ff..59fbe51af 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +#if DEBUG_LIMITS using System.Diagnostics; +#endif using System.IO; using System.Net; using System.Threading; @@ -40,14 +42,18 @@ namespace Discord.Net.Queue public async Task SendAsync(RestRequest request) { int id = Interlocked.Increment(ref nextId); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Start"); +#endif LastAttemptAt = DateTimeOffset.UtcNow; while (true) { await _queue.EnterGlobalAsync(id, request).ConfigureAwait(false); await EnterAsync(id, request).ConfigureAwait(false); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sending..."); +#endif var response = await request.SendAsync().ConfigureAwait(false); TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); var info = new RateLimitInfo(response.Headers); @@ -59,18 +65,24 @@ namespace Discord.Net.Queue case (HttpStatusCode)429: if (info.IsGlobal) { +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] (!) 429 [Global]"); +#endif _queue.PauseGlobal(info, lag); } else { +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] (!) 429"); +#endif UpdateRateLimit(id, request, info, lag, true); } await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); continue; //Retry case HttpStatusCode.BadGateway: //502 +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] (!) 502"); +#endif continue; //Continue default: string reason = null; @@ -92,9 +104,13 @@ namespace Discord.Net.Queue } else { +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Success"); +#endif UpdateRateLimit(id, request, info, lag, false); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Stop"); +#endif return response.Stream; } } @@ -135,7 +151,9 @@ namespace Discord.Net.Queue if (resetAt > timeoutAt) throw new RateLimitedException(); int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); +#endif if (millis > 0) await Task.Delay(millis, request.CancelToken).ConfigureAwait(false); } @@ -143,13 +161,17 @@ namespace Discord.Net.Queue { if ((timeoutAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds < 500.0) throw new RateLimitedException(); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping 500* ms (Pre-emptive)"); +#endif await Task.Delay(500, request.CancelToken).ConfigureAwait(false); } continue; } +#if DEBUG_LIMITS else Debug.WriteLine($"[{id}] Entered Semaphore ({_semaphore}/{WindowCount} remaining)"); +#endif break; } } @@ -166,7 +188,9 @@ namespace Discord.Net.Queue { WindowCount = info.Limit.Value; _semaphore = info.Remaining.Value; +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Upgraded Semaphore to {info.Remaining.Value}/{WindowCount}"); +#endif } var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -182,24 +206,32 @@ namespace Discord.Net.Queue { //RetryAfter is more accurate than Reset, where available resetTick = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Retry-After: {info.RetryAfter.Value} ({info.RetryAfter.Value} ms)"); +#endif } else if (info.Reset.HasValue) { resetTick = info.Reset.Value.AddSeconds(/*1.0 +*/ lag.TotalSeconds); int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)"); +#endif } else if (request.Options.ClientBucketId != null) { resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds); +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds * 1000} ms)"); +#endif } if (resetTick == null) { WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Disabled Semaphore"); +#endif return; } @@ -207,7 +239,9 @@ namespace Discord.Net.Queue { _resetTick = resetTick; LastAttemptAt = resetTick.Value; //Make sure we dont destroy this until after its been reset +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] Reset in {(int)Math.Ceiling((resetTick - DateTimeOffset.UtcNow).Value.TotalMilliseconds)} ms"); +#endif if (!hasQueuedReset) { @@ -227,7 +261,9 @@ namespace Discord.Net.Queue millis = (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds); if (millis <= 0) //Make sure we havent gotten a more accurate reset time { +#if DEBUG_LIMITS Debug.WriteLine($"[{id}] * Reset *"); +#endif _semaphore = WindowCount; _resetTick = null; return; From 91e6cb98c3ae8c3e6e382773a154ebd6f0dc5d9d Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 08:04:40 -0400 Subject: [PATCH 22/88] Merged request BucketId and ClientBucketId. Added IsClientBucket. --- .../API/DiscordRestApiClient.cs | 24 +++++++++---------- .../Net/Queue/RequestQueueBucket.cs | 10 ++++---- src/Discord.Net.Core/RequestOptions.cs | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 966cdd83a..553cbd1c3 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -172,8 +172,8 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var request = new RestRequest(_restClient, method, endpoint, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); @@ -187,8 +187,8 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(_restClient, method, endpoint, json, options); @@ -203,8 +203,8 @@ namespace Discord.API { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); @@ -217,8 +217,8 @@ namespace Discord.API string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var request = new RestRequest(_restClient, method, endpoint, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); @@ -231,8 +231,8 @@ namespace Discord.API string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(_restClient, method, endpoint, json, options); @@ -246,8 +246,8 @@ namespace Discord.API string bucketId = null, string clientBucketId = null, RequestOptions options = null) { options = options ?? new RequestOptions(); - options.BucketId = bucketId; - options.ClientBucketId = AuthTokenType == TokenType.User ? clientBucketId : null; + options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.IsClientBucket = AuthTokenType == TokenType.User; var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 59fbe51af..19976e998 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -29,8 +29,8 @@ namespace Discord.Net.Queue _lock = new object(); - if (request.Options.ClientBucketId != null) - WindowCount = ClientBucket.Get(request.Options.ClientBucketId).WindowCount; + if (request.Options.IsClientBucket) + WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount; else WindowCount = 1; //Only allow one request until we get a header back _semaphore = WindowCount; @@ -218,11 +218,11 @@ namespace Discord.Net.Queue Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {lag.TotalMilliseconds} ms lag)"); #endif } - else if (request.Options.ClientBucketId != null) + else if (request.Options.IsClientBucket && request.Options.BucketId != null) { - resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds); + resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds); #if DEBUG_LIMITS - Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.ClientBucketId).WindowSeconds * 1000} ms)"); + Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)"); #endif } diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 3af6c929d..195390ecf 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -10,7 +10,7 @@ internal bool IgnoreState { get; set; } internal string BucketId { get; set; } - internal string ClientBucketId { get; set; } + internal bool IsClientBucket { get; set; } internal static RequestOptions CreateOrClone(RequestOptions options) { From 4633735ae9c6c70beca371b4e1a934ebe66cc3f4 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 08:15:11 -0400 Subject: [PATCH 23/88] Created ClientBucketType enum --- .../API/DiscordRestApiClient.cs | 54 +++++++++---------- .../Net/Queue/ClientBucket.cs | 40 ++++++++++---- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 553cbd1c3..1a5b5b2fd 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -165,14 +165,14 @@ namespace Discord.API //Core internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var request = new RestRequest(_restClient, method, endpoint, options); @@ -180,14 +180,14 @@ namespace Discord.API } internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendJsonAsync(string method, string endpoint, object payload, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var json = payload != null ? SerializeJson(payload) : null; @@ -196,14 +196,14 @@ namespace Discord.API } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); @@ -211,13 +211,13 @@ namespace Discord.API } internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var request = new RestRequest(_restClient, method, endpoint, options); @@ -225,13 +225,13 @@ namespace Discord.API } internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendJsonAsync(string method, string endpoint, object payload, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var json = payload != null ? SerializeJson(payload) : null; @@ -240,13 +240,13 @@ namespace Discord.API } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, - string clientBucketId = null, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucketId, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - string bucketId = null, string clientBucketId = null, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); - options.BucketId = AuthTokenType == TokenType.User ? clientBucketId : bucketId; + options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; var request = new MultipartRestRequest(_restClient, method, endpoint, multipartArgs, options); @@ -445,7 +445,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) { @@ -464,7 +464,7 @@ namespace Discord.API } var ids = new BucketIds(channelId: channelId); - return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); + return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -510,7 +510,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucketId: ClientBucket.SendEditId, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { diff --git a/src/Discord.Net.Core/Net/Queue/ClientBucket.cs b/src/Discord.Net.Core/Net/Queue/ClientBucket.cs index 14d3c3207..f32df1bcf 100644 --- a/src/Discord.Net.Core/Net/Queue/ClientBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/ClientBucket.cs @@ -2,25 +2,47 @@ namespace Discord.Net.Queue { - public struct ClientBucket + public enum ClientBucketType { - public const string SendEditId = ""; + Unbucketed = 0, + SendEdit = 1 + } + internal struct ClientBucket + { + private static readonly ImmutableDictionary _defsByType; + private static readonly ImmutableDictionary _defsById; - private static readonly ImmutableDictionary _defs; static ClientBucket() { - var builder = ImmutableDictionary.CreateBuilder(); - builder.Add(SendEditId, new ClientBucket(10, 10)); - _defs = builder.ToImmutable(); - } + var buckets = new[] + { + new ClientBucket(ClientBucketType.Unbucketed, "", 10, 10), + new ClientBucket(ClientBucketType.SendEdit, "", 10, 10) + }; - public static ClientBucket Get(string id) =>_defs[id]; + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var bucket in buckets) + builder.Add(bucket.Type, bucket); + _defsByType = builder.ToImmutable(); + + var builder2 = ImmutableDictionary.CreateBuilder(); + foreach (var bucket in buckets) + builder2.Add(bucket.Id, bucket); + _defsById = builder2.ToImmutable(); + } + public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; + public static ClientBucket Get(string id) => _defsById[id]; + + public ClientBucketType Type { get; } + public string Id { get; } public int WindowCount { get; } public int WindowSeconds { get; } - public ClientBucket(int count, int seconds) + public ClientBucket(ClientBucketType type, string id, int count, int seconds) { + Type = type; + Id = id; WindowCount = count; WindowSeconds = seconds; } From b2948deaf79931cb6a1bbbb71fa1715a2b637b18 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 08:43:12 -0400 Subject: [PATCH 24/88] Fixed unused timeout const, dropped default request timeout to 15s. --- src/Discord.Net.Core/DiscordConfig.cs | 1 + src/Discord.Net.Core/RequestOptions.cs | 2 +- src/Discord.Net.Rest/DiscordRestConfig.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 737cf0050..0cb190726 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -14,6 +14,7 @@ namespace Discord public const string CDNUrl = "https://discordcdn.com/"; public const string InviteUrl = "https://discord.gg/"; + public const int DefaultRequestTimeout = 15000; public const int MaxMessageSize = 2000; public const int MaxMessagesPerBatch = 100; public const int MaxUsersPerBatch = 1000; diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 195390ecf..b82ec29c8 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -22,7 +22,7 @@ public RequestOptions() { - Timeout = 30000; + Timeout = DiscordConfig.DefaultRequestTimeout; } public RequestOptions Clone() => MemberwiseClone() as RequestOptions; diff --git a/src/Discord.Net.Rest/DiscordRestConfig.cs b/src/Discord.Net.Rest/DiscordRestConfig.cs index 8dee72231..33a3cb4e8 100644 --- a/src/Discord.Net.Rest/DiscordRestConfig.cs +++ b/src/Discord.Net.Rest/DiscordRestConfig.cs @@ -6,7 +6,6 @@ namespace Discord.Rest { public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; - internal const int RestTimeout = 10000; internal const int MessageQueueInterval = 100; internal const int WebSocketQueueInterval = 100; From f95154af234ef01c144eca14d3d56fcf3100e713 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Mon, 14 Nov 2016 19:26:32 +0000 Subject: [PATCH 25/88] Start work on command builders Right now commands require a module before they can be created; I'm looking to remove this in the future. --- .../Builders/CommandBuilder.cs | 69 ++++++++++++ .../Builders/ModuleBuilder.cs | 102 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/Discord.Net.Commands/Builders/CommandBuilder.cs create mode 100644 src/Discord.Net.Commands/Builders/ModuleBuilder.cs diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs new file mode 100644 index 000000000..e14743499 --- /dev/null +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Discord.Commands +{ + public class CommandBuilder + { + private List aliases; + + internal CommandBuilder(ModuleBuilder module, string prefix) + { + aliases = new List(); + + if (prefix != null) + { + aliases.Add(prefix); + Name = prefix; + } + + Module = module; + } + + + public string Name { get; set; } + public string Summary { get; set; } + public string Remarks { get; set; } + public Func Callback { get; set; } + public ModuleBuilder Module { get; } + + public List Aliases => aliases; + + + public CommandBuilder SetName(string name) + { + Name = name; + return this; + } + + public CommandBuilder SetSummary(string summary) + { + Summary = summary; + return this; + } + + public CommandBuilder SetRemarks(string remarks) + { + Remarks = remarks; + return this; + } + + public CommandBuilder SetCallback(Func callback) + { + Callback = callback; + return this; + } + + public CommandBuilder AddAlias(string alias) + { + aliases.Add(alias); + return this; + } + + public ModuleBuilder Done() + { + return Module; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs new file mode 100644 index 000000000..ca4f84633 --- /dev/null +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Discord.Commands +{ + public class ModuleBuilder + { + private List commands; + private List submodules; + private List aliases; + + + public ModuleBuilder() + : this(null, null) + { } + + public ModuleBuilder(string prefix) + : this(null, prefix) + { } + + internal ModuleBuilder(ModuleBuilder parent) + : this(parent, null) + { } + + internal ModuleBuilder(ModuleBuilder parent, string prefix) + { + commands = new List(); + submodules = new List(); + aliases = new List(); + + if (prefix != null) + { + aliases.Add(prefix); + Name = prefix; + } + + ParentModule = parent; + } + + + public string Name { get; set; } + public string Summary { get; set; } + public string Remarks { get; set; } + public ModuleBuilder ParentModule { get; } + + public List Commands => commands; + public List Modules => submodules; + public List Aliases => aliases; + + + public ModuleBuilder SetName(string name) + { + Name = name; + return this; + } + + public ModuleBuilder SetSummary(string summary) + { + Summary = summary; + return this; + } + + public ModuleBuilder SetRemarks(string remarks) + { + Remarks = remarks; + return this; + } + + public ModuleBuilder AddAlias(string alias) + { + aliases.Add(alias); + return this; + } + + public CommandBuilder AddCommand() => AddCommand(null); + public CommandBuilder AddCommand(string name) + { + var builder = new CommandBuilder(this, name); + commands.Add(builder); + + return builder; + } + + public ModuleBuilder AddSubmodule() => AddSubmodule(null); + public ModuleBuilder AddSubmodule(string prefix) + { + var builder = new ModuleBuilder(this, prefix); + submodules.Add(builder); + + return builder; + } + + public ModuleBuilder Done() + { + if (ParentModule == null) + throw new InvalidOperationException("Cannot finish a top-level module!"); + + return ParentModule; + } + } +} From c2722cf7c459457c2b993f5a0b6486cbf5b7f353 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 20:05:43 -0400 Subject: [PATCH 26/88] SendMessage should accept EmbedBuilder directly --- src/Discord.Net.Commands/ModuleBase.cs | 2 +- src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 4 ++-- .../Entities/Channels/RestVirtualMessageChannel.cs | 4 ++-- src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs | 4 ++-- src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs | 4 ++-- src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs | 4 ++-- .../Entities/Channels/ISocketMessageChannel.cs | 2 +- .../Entities/Channels/SocketDMChannel.cs | 4 ++-- .../Entities/Channels/SocketGroupChannel.cs | 4 ++-- .../Entities/Channels/SocketTextChannel.cs | 4 ++-- 15 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 73770402e..96dedcb23 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -6,7 +6,7 @@ namespace Discord.Commands { public CommandContext Context { get; internal set; } - protected virtual async Task ReplyAsync(string message, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + protected virtual async Task ReplyAsync(string message, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) { return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index 30ca044bd..41bc79511 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -8,7 +8,7 @@ namespace Discord public interface IMessageChannel : IChannel { /// Sends a message to this message channel. - Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); + Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index aecdf292e..9076efe57 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -110,9 +110,9 @@ namespace Discord.Rest } public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, - string text, bool isTTS, API.Embed embed, IGuild guild, RequestOptions options) + string text, bool isTTS, EmbedBuilder embed, IGuild guild, RequestOptions options) { - var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed }; + var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed.Build() }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, guild, model); } diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index 82783700e..554104d4d 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -7,7 +7,7 @@ namespace Discord.Rest public interface IRestMessageChannel : IMessageChannel { /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 8c547ad80..cf5488a7e 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -63,7 +63,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -126,7 +126,7 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index eb29192c4..1c7407f3b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -76,7 +76,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -136,7 +136,7 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 5c6fc371b..ad4c0037b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -55,7 +55,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -108,7 +108,7 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs index a99430a82..876447968 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -33,7 +33,7 @@ namespace Discord.Rest public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -86,7 +86,7 @@ namespace Discord.Rest => await SendFileAsync(filePath, text, isTTS, options); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index e9393b5f9..37e8de25b 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -44,7 +44,7 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -104,7 +104,7 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index 12fb91343..aae1a28f4 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -46,7 +46,7 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -103,7 +103,7 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index 7b20660ab..a3cc6bf3b 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -49,7 +49,7 @@ namespace Discord.Rpc public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -105,7 +105,7 @@ namespace Discord.Rpc => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index ed22c78a8..f1d4221a2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - new Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index e9d37cc9c..806f39254 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -66,7 +66,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -134,7 +134,7 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 723df5390..feb7103fe 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -89,7 +89,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); @@ -197,7 +197,7 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 693af4312..42b7ae373 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -72,7 +72,7 @@ namespace Discord.WebSocket public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, Guild, options); - public Task SendMessageAsync(string text, bool isTTS = false, API.Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, Guild, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, Guild, options); @@ -135,7 +135,7 @@ namespace Discord.WebSocket => await SendFileAsync(filePath, text, isTTS, options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, API.Embed embed, RequestOptions options) + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, EmbedBuilder embed, RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); From af6c3e10c37297dd6279e237cfb4d94cb549c335 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 20:16:24 -0400 Subject: [PATCH 27/88] Cleaned up EmbedBuilder --- src/Discord.Net.Core/Utils/EmbedBuilder.cs | 140 +++++++-------------- 1 file changed, 45 insertions(+), 95 deletions(-) diff --git a/src/Discord.Net.Core/Utils/EmbedBuilder.cs b/src/Discord.Net.Core/Utils/EmbedBuilder.cs index 42861709f..dca4377eb 100644 --- a/src/Discord.Net.Core/Utils/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Utils/EmbedBuilder.cs @@ -9,124 +9,74 @@ namespace Discord { public class EmbedBuilder { - private Embed embed = new Embed(); - List fields = new List(); + private Embed model = new Embed(); + private List fields = new List(); public EmbedBuilder() { - embed.Type = "rich"; + model.Type = "rich"; } - public EmbedBuilder Title(string title) - { - embed.Title = title; - return this; - } - public EmbedBuilder Description(string description) - { - embed.Description = description; - return this; - } - public EmbedBuilder Url(string url) - { - embed.Url = url; - return this; - } - public EmbedBuilder Color(Color color) - { - embed.Color = color.RawValue; - return this; - } - public EmbedBuilder Field(Func builder) + public string Title { get { return model.Title; } set { model.Title = value; } } + public string Description { get { return model.Description; } set { model.Description = value; } } + public string Url { get { return model.Url; } set { model.Url = value; } } + public Color? Color { get { return model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null; } set { model.Color = value?.RawValue; } } + + public void SetAuthor(Action action) { - fields.Add(builder(new EmbedFieldBuilder()).Build()); - return this; + var author = new EmbedBuilderAuthor(); + action(author); + model.Author = author.ToModel(); } - public EmbedBuilder Author(Func builder) + public void SetFooter(Action action) { - embed.Author = builder(new EmbedAuthorBuilder()).Build(); - return this; + var footer = new EmbedBuilderFooter(); + action(footer); + model.Footer = footer.ToModel(); } - public EmbedBuilder Footer(Func builder) + public void AddField(Action action) { - embed.Footer = builder(new EmbedFooterBuilder()).Build(); - return this; + var field = new EmbedBuilderField(); + action(field); + fields.Add(field.ToModel()); } - public Embed Build() + + internal Embed Build() { - embed.Fields = fields.ToArray(); - return embed; + model.Fields = fields.ToArray(); + return model; } - } - public class EmbedFieldBuilder + public class EmbedBuilderField { - private Field embedField = new Field(); + private Field model = new Field(); - public EmbedFieldBuilder Name(string name) - { - embedField.Name = name; - return this; - } - public EmbedFieldBuilder Value(string value) - { - embedField.Value = value; - return this; - } - public EmbedFieldBuilder Inline(bool inline) - { - embedField.Inline = inline; - return this; - } - public Field Build() - { - return embedField; - } + public string Name { get { return model.Name; } set { model.Name = value; } } + public string Value { get { return model.Value; } set { model.Value = value; } } + public bool IsInline { get { return model.Inline; } set { model.Inline = value; } } + + internal Field ToModel() => model; } - public class EmbedAuthorBuilder + public class EmbedBuilderAuthor { - private Author author = new Author(); - - public EmbedAuthorBuilder Name(string name) - { - author.Name = name; - return this; - } - public EmbedAuthorBuilder Url(string url) - { - author.Url = url; - return this; - } - public EmbedAuthorBuilder IconUrl(string iconUrl) - { - author.IconUrl = iconUrl; - return this; - } - public Author Build() - { - return author; - } + private Author model = new Author(); + + public string Name { get { return model.Name; } set { model.Name = value; } } + public string Url { get { return model.Url; } set { model.Url = value; } } + public string IconUrl { get { return model.IconUrl; } set { model.IconUrl = value; } } + + internal Author ToModel() => model; } - public class EmbedFooterBuilder + public class EmbedBuilderFooter { - private Footer footer = new Footer(); + private Footer model = new Footer(); - public EmbedFooterBuilder Text(string text) - { - footer.Text = text; - return this; - } - public EmbedFooterBuilder IconUrl(string iconUrl) - { - footer.IconUrl = iconUrl; - return this; - } - public Footer Build() - { - return footer; - } + public string Text { get { return model.Text; } set { model.Text = value; } } + public string IconUrl { get { return model.IconUrl; } set { model.IconUrl = value; } } + + internal Footer ToModel() => model; } } From bdb44f3606c2235f20b639856ef2d082a18c1b34 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 20:28:34 -0400 Subject: [PATCH 28/88] Minor edit --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index 8535fcf33..d050f12a4 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -432,7 +432,7 @@ namespace Discord.API if (relativeId != null) endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; else - endpoint = () =>$"channels/{channelId}/messages?limit={limit}"; + endpoint = () => $"channels/{channelId}/messages?limit={limit}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) From cabbd0de89050fc13cc4a02b71339c3b1314fb34 Mon Sep 17 00:00:00 2001 From: RogueException Date: Mon, 14 Nov 2016 22:48:55 -0400 Subject: [PATCH 29/88] Dont nullref in SendMessageAsync if no embed is provided --- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 9076efe57..e51a56370 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -112,7 +112,7 @@ namespace Discord.Rest public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, string text, bool isTTS, EmbedBuilder embed, IGuild guild, RequestOptions options) { - var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed.Build() }; + var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.Build() }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, guild, model); } From 13f48424d597e0002541e3f4f60433f3b6ebfe39 Mon Sep 17 00:00:00 2001 From: zenima Date: Tue, 15 Nov 2016 09:06:15 +0100 Subject: [PATCH 30/88] Updated ModifyStatus sample --- docs/guides/samples/faq/status.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/guides/samples/faq/status.cs b/docs/guides/samples/faq/status.cs index 4d6a29924..8025dd7fd 100644 --- a/docs/guides/samples/faq/status.cs +++ b/docs/guides/samples/faq/status.cs @@ -1,8 +1,5 @@ public async Task ModifyStatus() { - await (await _client.GetCurrentUserAsync()).ModifyStatusAsync(x => - { - x.Status = UserStatus.Idle; - x.Game = new Game("Type !help for help"); - }); -} \ No newline at end of file + await _client.SetStatus(UserStatus.Idle); + await _client.SetGame("Type !help for help"); +} From b82666b47f7738a20d0feef4f0b5cae4bb102602 Mon Sep 17 00:00:00 2001 From: zenima Date: Tue, 15 Nov 2016 09:07:49 +0100 Subject: [PATCH 31/88] Fixed a typo which prevented syntax highlight in samples/faq/status.cs --- docs/guides/samples.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/samples.md b/docs/guides/samples.md index ef85e4898..4406f2f1e 100644 --- a/docs/guides/samples.md +++ b/docs/guides/samples.md @@ -13,8 +13,8 @@ title: Samples #### Changing the bot's status -[!code-sharp[Bot Status](samples/faq/status.cs)] +[!code-csharp[Bot Status](samples/faq/status.cs)] #### Sending a message to a channel -[!code-csharp[Message to Channel](samples/faq/send_message.cs)] \ No newline at end of file +[!code-csharp[Message to Channel](samples/faq/send_message.cs)] From af433c82ccf9d5de28008af1d2f7399124a2ebc1 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Tue, 15 Nov 2016 20:53:18 +0000 Subject: [PATCH 32/88] Complete builders, start work on using them --- .../Builders/CommandBuilder.cs | 30 +- .../Builders/ModuleBuilder.cs | 21 +- .../Builders/ParameterBuilder.cs | 86 ++++++ src/Discord.Net.Commands/CommandInfo.cs | 284 ------------------ src/Discord.Net.Commands/CommandParser.cs | 2 +- .../Extensions/IEnumerableExtensions.cs | 22 ++ src/Discord.Net.Commands/Info/CommandInfo.cs | 173 +++++++++++ src/Discord.Net.Commands/Info/ModuleInfo.cs | 74 +++++ .../ParameterInfo.cs} | 45 +-- src/Discord.Net.Commands/ModuleInfo.cs | 85 ------ src/Discord.Net.Commands/ReflectionUtils.cs | 2 +- 11 files changed, 416 insertions(+), 408 deletions(-) create mode 100644 src/Discord.Net.Commands/Builders/ParameterBuilder.cs delete mode 100644 src/Discord.Net.Commands/CommandInfo.cs create mode 100644 src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs create mode 100644 src/Discord.Net.Commands/Info/CommandInfo.cs create mode 100644 src/Discord.Net.Commands/Info/ModuleInfo.cs rename src/Discord.Net.Commands/{CommandParameter.cs => Info/ParameterInfo.cs} (50%) delete mode 100644 src/Discord.Net.Commands/ModuleInfo.cs diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index e14743499..077787427 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -2,14 +2,18 @@ using System.Threading.Tasks; using System.Collections.Generic; -namespace Discord.Commands +namespace Discord.Commands.Builders { public class CommandBuilder { + private List preconditions; + private List parameters; private List aliases; internal CommandBuilder(ModuleBuilder module, string prefix) { + preconditions = new List(); + parameters = new List(); aliases = new List(); if (prefix != null) @@ -21,16 +25,16 @@ namespace Discord.Commands Module = module; } - public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public Func Callback { get; set; } + public Func Callback { get; set; } public ModuleBuilder Module { get; } + public List Preconditions => preconditions; + public List Parameters => parameters; public List Aliases => aliases; - public CommandBuilder SetName(string name) { Name = name; @@ -49,21 +53,33 @@ namespace Discord.Commands return this; } - public CommandBuilder SetCallback(Func callback) + public CommandBuilder SetCallback(Func callback) { Callback = callback; return this; } + public CommandBuilder AddPrecondition(PreconditionAttribute precondition) + { + preconditions.Add(precondition); + return this; + } + + public CommandBuilder AddParameter(ParameterBuilder parameter) + { + parameters.Add(parameter); + return this; + } + public CommandBuilder AddAlias(string alias) { aliases.Add(alias); return this; } - public ModuleBuilder Done() + internal CommandInfo Build(ModuleInfo info, CommandService service) { - return Module; + return new CommandInfo(this, info, service); } } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index ca4f84633..0375383a8 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -2,15 +2,15 @@ using System.Collections.Generic; using System.Collections.Immutable; -namespace Discord.Commands +namespace Discord.Commands.Builders { public class ModuleBuilder { private List commands; private List submodules; + private List preconditions; private List aliases; - public ModuleBuilder() : this(null, null) { } @@ -27,6 +27,7 @@ namespace Discord.Commands { commands = new List(); submodules = new List(); + preconditions = new List(); aliases = new List(); if (prefix != null) @@ -38,7 +39,6 @@ namespace Discord.Commands ParentModule = parent; } - public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } @@ -46,9 +46,9 @@ namespace Discord.Commands public List Commands => commands; public List Modules => submodules; + public List Preconditions => preconditions; public List Aliases => aliases; - public ModuleBuilder SetName(string name) { Name = name; @@ -73,6 +73,12 @@ namespace Discord.Commands return this; } + public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) + { + preconditions.Add(precondition); + return this; + } + public CommandBuilder AddCommand() => AddCommand(null); public CommandBuilder AddCommand(string name) { @@ -91,12 +97,9 @@ namespace Discord.Commands return builder; } - public ModuleBuilder Done() + public ModuleInfo Build(CommandService service) { - if (ParentModule == null) - throw new InvalidOperationException("Cannot finish a top-level module!"); - - return ParentModule; + return new ModuleInfo(this, service); } } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs new file mode 100644 index 000000000..e90b2a9bf --- /dev/null +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Discord.Commands.Builders +{ + public class ParameterBuilder + { + public ParameterBuilder() + { } + + public ParameterBuilder(string name) + { + Name = name; + } + + public string Name { get; set; } + public string Summary { get; set; } + public object DefaultValue { get; set; } + public Type ParameterType { get; set; } + + public TypeReader TypeReader { get; set; } + + public bool Optional { get; set; } + public bool Remainder { get; set; } + public bool Multiple { get; set; } + + public ParameterBuilder SetName(string name) + { + Name = name; + return this; + } + + public ParameterBuilder SetSummary(string summary) + { + Summary = summary; + return this; + } + + public ParameterBuilder SetDefault(T defaultValue) + { + DefaultValue = defaultValue; + ParameterType = typeof(T); + + if (ParameterType.IsArray) + ParameterType = ParameterType.GetElementType(); + + return this; + } + + public ParameterBuilder SetType(Type parameterType) + { + ParameterType = parameterType; + return this; + } + + public ParameterBuilder SetTypeReader(TypeReader reader) + { + TypeReader = reader; + return this; + } + + public ParameterBuilder SetOptional(bool isOptional) + { + Optional = isOptional; + return this; + } + + public ParameterBuilder SetRemainder(bool isRemainder) + { + Remainder = isRemainder; + return this; + } + + public ParameterBuilder SetMultiple(bool isMultiple) + { + Multiple = isMultiple; + return this; + } + + internal ParameterInfo Build(CommandInfo info, CommandService service) + { + return new ParameterInfo(this, info, service); + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandInfo.cs b/src/Discord.Net.Commands/CommandInfo.cs deleted file mode 100644 index 47aae1ae2..000000000 --- a/src/Discord.Net.Commands/CommandInfo.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class CommandInfo - { - private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); - private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - - private readonly Func _action; - - public MethodInfo Source { get; } - public ModuleInfo Module { get; } - public string Name { get; } - public string Summary { get; } - public string Remarks { get; } - public string Text { get; } - public int Priority { get; } - public bool HasVarArgs { get; } - public RunMode RunMode { get; } - public IReadOnlyList Aliases { get; } - public IReadOnlyList Parameters { get; } - public IReadOnlyList Preconditions { get; } - - internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) - { - try - { - Source = source; - Module = module; - - Name = source.Name; - - if (attribute.Text == null) - Text = groupPrefix; - RunMode = attribute.RunMode; - - if (groupPrefix != "") - groupPrefix += " "; - - if (attribute.Text != null) - Text = groupPrefix + attribute.Text; - - var aliasesBuilder = ImmutableArray.CreateBuilder(); - - aliasesBuilder.Add(Text); - - var aliasesAttr = source.GetCustomAttribute(); - if (aliasesAttr != null) - aliasesBuilder.AddRange(aliasesAttr.Aliases.Select(x => groupPrefix + x)); - - Aliases = aliasesBuilder.ToImmutable(); - - var nameAttr = source.GetCustomAttribute(); - if (nameAttr != null) - Name = nameAttr.Text; - - var summary = source.GetCustomAttribute(); - if (summary != null) - Summary = summary.Text; - - var remarksAttr = source.GetCustomAttribute(); - if (remarksAttr != null) - Remarks = remarksAttr.Text; - - var priorityAttr = source.GetCustomAttribute(); - Priority = priorityAttr?.Priority ?? 0; - - Parameters = BuildParameters(source); - HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; - Preconditions = BuildPreconditions(source); - _action = BuildAction(source); - } - catch (Exception ex) - { - throw new Exception($"Failed to build command {source.DeclaringType.FullName}.{source.Name}", ex); - } - } - - public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) - { - if (map == null) - map = DependencyMap.Empty; - - foreach (PreconditionAttribute precondition in Module.Preconditions) - { - var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - foreach (PreconditionAttribute precondition in Preconditions) - { - var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); - if (!result.IsSuccess) - return result; - } - - return PreconditionResult.FromSuccess(); - } - - public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) - { - if (!searchResult.IsSuccess) - return ParseResult.FromError(searchResult); - if (preconditionResult != null && !preconditionResult.Value.IsSuccess) - return ParseResult.FromError(preconditionResult.Value); - - string input = searchResult.Text; - var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); - - string matchingAlias = ""; - foreach (string alias in matchingAliases) - { - if (alias.Length > matchingAlias.Length) - matchingAlias = alias; - } - - input = input.Substring(matchingAlias.Length); - - return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); - } - public Task Execute(CommandContext context, ParseResult parseResult, IDependencyMap map) - { - if (!parseResult.IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult)); - - var argList = new object[parseResult.ArgValues.Count]; - for (int i = 0; i < parseResult.ArgValues.Count; i++) - { - if (!parseResult.ArgValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); - argList[i] = parseResult.ArgValues[i].Values.First().Value; - } - - var paramList = new object[parseResult.ParamValues.Count]; - for (int i = 0; i < parseResult.ParamValues.Count; i++) - { - if (!parseResult.ParamValues[i].IsSuccess) - return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); - paramList[i] = parseResult.ParamValues[i].Values.First().Value; - } - - return Execute(context, argList, paramList, map); - } - public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) - { - if (map == null) - map = DependencyMap.Empty; - - try - { - var args = GenerateArgs(argList, paramList); - switch (RunMode) - { - case RunMode.Sync: //Always sync - await _action(context, args, map).ConfigureAwait(false); - break; - case RunMode.Mixed: //Sync until first await statement - var t1 = _action(context, args, map); - break; - case RunMode.Async: //Always async - var t2 = Task.Run(() => _action(context, args, map)); - break; - } - return ExecuteResult.FromSuccess(); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } - } - - private IReadOnlyList BuildPreconditions(MethodInfo methodInfo) - { - return methodInfo.GetCustomAttributes().ToImmutableArray(); - } - - private IReadOnlyList BuildParameters(MethodInfo methodInfo) - { - var parameters = methodInfo.GetParameters(); - - var paramBuilder = ImmutableArray.CreateBuilder(parameters.Length); - for (int i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var type = parameter.ParameterType; - - //Detect 'params' - bool isMultiple = parameter.GetCustomAttribute() != null; - if (isMultiple) - type = type.GetElementType(); - - var reader = Module.Service.GetTypeReader(type); - var typeInfo = type.GetTypeInfo(); - - //Detect enums - if (reader == null && typeInfo.IsEnum) - { - reader = EnumTypeReader.GetReader(type); - Module.Service.AddTypeReader(type, reader); - } - - if (reader == null) - throw new InvalidOperationException($"{type.FullName} is not supported as a command parameter, are you missing a TypeReader?"); - - bool isRemainder = parameter.GetCustomAttribute() != null; - if (isRemainder && i != parameters.Length - 1) - throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); - - string name = parameter.Name; - string summary = parameter.GetCustomAttribute()?.Text; - bool isOptional = parameter.IsOptional; - object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null; - - paramBuilder.Add(new CommandParameter(parameters[i], name, summary, type, reader, isOptional, isRemainder, isMultiple, defaultValue)); - } - return paramBuilder.ToImmutable(); - } - private Func BuildAction(MethodInfo methodInfo) - { - if (methodInfo.ReturnType != typeof(Task)) - throw new InvalidOperationException("Commands must return a non-generic Task."); - - return (context, args, map) => - { - var instance = Module.CreateInstance(map); - instance.Context = context; - try - { - return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask; - } - finally - { - (instance as IDisposable)?.Dispose(); - } - }; - } - - private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) - { - int argCount = Parameters.Count; - var array = new object[Parameters.Count]; - if (HasVarArgs) - argCount--; - - int i = 0; - foreach (var arg in argList) - { - if (i == argCount) - throw new InvalidOperationException("Command was invoked with too many parameters"); - array[i++] = arg; - } - if (i < argCount) - throw new InvalidOperationException("Command was invoked with too few parameters"); - - if (HasVarArgs) - { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ElementType, t => - { - var method = _convertParamsMethod.MakeGenericMethod(t); - return (Func, object>)method.CreateDelegate(typeof(Func, object>)); - }); - array[i] = func(paramsList); - } - - return array; - } - - private static T[] ConvertParamsList(IEnumerable paramsList) - => paramsList.Cast().ToArray(); - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})"; - } -} diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 1808b705d..7bdbed955 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -15,7 +15,7 @@ namespace Discord.Commands public static async Task ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) { - CommandParameter curParam = null; + ParameterInfo curParam = null; StringBuilder argBuilder = new StringBuilder(input.Length); int endPos = input.Length; var curPart = ParserPart.None; diff --git a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs new file mode 100644 index 000000000..b922dd903 --- /dev/null +++ b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Commands +{ + public static class IEnumerableExtensions + { + public static IEnumerable Permutate( + this IEnumerable set, + IEnumerable others, + Func func) + { + foreach (TFirst elem in set) + { + foreach (TSecond elem2 in others) + { + yield return func(elem, elem2); + } + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs new file mode 100644 index 000000000..fc1e0421b --- /dev/null +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using System.Reflection; + +using Discord.Commands.Builders; + +namespace Discord.Commands +{ + public class CommandInfo + { + private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); + private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); + + private readonly Func _action; + + public ModuleInfo Module { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + public int Priority { get; } + public bool HasVarArgs { get; } + public RunMode RunMode { get; } + + public IReadOnlyList Aliases { get; } + public IReadOnlyList Parameters { get; } + public IReadOnlyList Preconditions { get; } + + internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) + { + Module = module; + + Name = builder.Name; + Summary = builder.Summary; + Remarks = builder.Remarks; + + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); + Preconditions = builder.Preconditions.ToImmutableArray(); + + Parameters = builder.Parameters.Select(x => x.Build(this, service)).ToImmutableArray(); + + _action = builder.Callback; + } + + public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) + { + if (map == null) + map = DependencyMap.Empty; + + foreach (PreconditionAttribute precondition in Module.Preconditions) + { + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + foreach (PreconditionAttribute precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + return PreconditionResult.FromSuccess(); + } + + public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + { + if (!searchResult.IsSuccess) + return ParseResult.FromError(searchResult); + if (preconditionResult != null && !preconditionResult.Value.IsSuccess) + return ParseResult.FromError(preconditionResult.Value); + + string input = searchResult.Text; + var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); + + string matchingAlias = ""; + foreach (string alias in matchingAliases) + { + if (alias.Length > matchingAlias.Length) + matchingAlias = alias; + } + + input = input.Substring(matchingAlias.Length); + + return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); + } + + public Task Execute(CommandContext context, ParseResult parseResult) + { + if (!parseResult.IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult)); + + var argList = new object[parseResult.ArgValues.Count]; + for (int i = 0; i < parseResult.ArgValues.Count; i++) + { + if (!parseResult.ArgValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i])); + argList[i] = parseResult.ArgValues[i].Values.First().Value; + } + + var paramList = new object[parseResult.ParamValues.Count]; + for (int i = 0; i < parseResult.ParamValues.Count; i++) + { + if (!parseResult.ParamValues[i].IsSuccess) + return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i])); + paramList[i] = parseResult.ParamValues[i].Values.First().Value; + } + + return Execute(context, argList, paramList); + } + public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList) + { + try + { + var args = GenerateArgs(argList, paramList); + switch (RunMode) + { + case RunMode.Sync: //Always sync + await _action(context, args).ConfigureAwait(false); + break; + case RunMode.Mixed: //Sync until first await statement + var t1 = _action(context, args); + break; + case RunMode.Async: //Always async + var t2 = Task.Run(() => _action(context, args)); + break; + } + return ExecuteResult.FromSuccess(); + } + catch (Exception ex) + { + return ExecuteResult.FromError(ex); + } + } + + private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) + { + int argCount = Parameters.Count; + var array = new object[Parameters.Count]; + if (HasVarArgs) + argCount--; + + int i = 0; + foreach (var arg in argList) + { + if (i == argCount) + throw new InvalidOperationException("Command was invoked with too many parameters"); + array[i++] = arg; + } + if (i < argCount) + throw new InvalidOperationException("Command was invoked with too few parameters"); + + if (HasVarArgs) + { + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ParameterType, t => + { + var method = _convertParamsMethod.MakeGenericMethod(t); + return (Func, object>)method.CreateDelegate(typeof(Func, object>)); + }); + array[i] = func(paramsList); + } + + return array; + } + + private static T[] ConvertParamsList(IEnumerable paramsList) + => paramsList.Cast().ToArray(); + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs new file mode 100644 index 000000000..33a6574c4 --- /dev/null +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; + +using Discord.Commands.Builders; + +namespace Discord.Commands +{ + public class ModuleInfo + { + public CommandService Service { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + + public IReadOnlyList Aliases { get; } + public IEnumerable Commands { get; } + public IReadOnlyList Preconditions { get; } + + internal ModuleInfo(ModuleBuilder builder, CommandService service) + { + Service = service; + + Name = builder.Name; + Summary = builder.Summary; + Remarks = builder.Remarks; + + Aliases = BuildAliases(builder).ToImmutableArray(); + Commands = builder.Commands.Select(x => x.Build(this, service)); + Preconditions = BuildPreconditions(builder).ToImmutableArray(); + } + + private static List BuildAliases(ModuleBuilder builder) + { + IEnumerable result = null; + + Stack builderStack = new Stack(); + + ModuleBuilder parent = builder; + while (parent.ParentModule != null) + { + builderStack.Push(parent); + parent = parent.ParentModule; + } + + while (builderStack.Count() > 0) + { + ModuleBuilder level = builderStack.Pop(); // get the topmost builder + if (result == null) + result = level.Aliases.ToList(); // create a shallow copy so we don't overwrite the builder unexpectedly + else + result = result.Permutate(level.Aliases, (first, second) => first + " " + second); + } + + return result.ToList(); + } + + private static List BuildPreconditions(ModuleBuilder builder) + { + var result = new List(); + + + ModuleBuilder parent = builder; + while (parent.ParentModule != null) + { + result.AddRange(parent.Preconditions); + parent = parent.ParentModule; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandParameter.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs similarity index 50% rename from src/Discord.Net.Commands/CommandParameter.cs rename to src/Discord.Net.Commands/Info/ParameterInfo.cs index 1edf42bf1..812fec572 100644 --- a/src/Discord.Net.Commands/CommandParameter.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -1,37 +1,40 @@ -using System; -using System.Diagnostics; -using System.Reflection; +using System; +using System.Linq; using System.Threading.Tasks; +using Discord.Commands.Builders; + namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class CommandParameter + public class ParameterInfo { private readonly TypeReader _reader; - public ParameterInfo Source { get; } + internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) + { + Command = command; + + Name = builder.Name; + Summary = builder.Summary; + IsOptional = builder.Optional; + IsRemainder = builder.Remainder; + IsMultiple = builder.Multiple; + + ParameterType = builder.ParameterType; + DefaultValue = builder.DefaultValue; + + _reader = builder.TypeReader; + } + + public CommandInfo Command { get; } public string Name { get; } public string Summary { get; } public bool IsOptional { get; } public bool IsRemainder { get; } public bool IsMultiple { get; } - public Type ElementType { get; } + public Type ParameterType { get; } public object DefaultValue { get; } - public CommandParameter(ParameterInfo source, string name, string summary, Type type, TypeReader reader, bool isOptional, bool isRemainder, bool isMultiple, object defaultValue) - { - Source = source; - Name = name; - Summary = summary; - ElementType = type; - _reader = reader; - IsOptional = isOptional; - IsRemainder = isRemainder; - IsMultiple = isMultiple; - DefaultValue = defaultValue; - } - public async Task Parse(CommandContext context, string input) { return await _reader.Read(context, input).ConfigureAwait(false); @@ -40,4 +43,4 @@ namespace Discord.Commands public override string ToString() => Name; private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; } -} +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/ModuleInfo.cs b/src/Discord.Net.Commands/ModuleInfo.cs deleted file mode 100644 index b7471edb5..000000000 --- a/src/Discord.Net.Commands/ModuleInfo.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Reflection; - -namespace Discord.Commands -{ - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class ModuleInfo - { - internal readonly Func _builder; - - public TypeInfo Source { get; } - public CommandService Service { get; } - public string Name { get; } - public string Prefix { get; } - public string Summary { get; } - public string Remarks { get; } - public IEnumerable Commands { get; } - public IReadOnlyList Preconditions { get; } - - internal ModuleInfo(TypeInfo source, CommandService service) - { - Source = source; - Service = service; - Name = source.Name; - _builder = ReflectionUtils.CreateBuilder(source, Service); - - var groupAttr = source.GetCustomAttribute(); - if (groupAttr != null) - Prefix = groupAttr.Prefix; - else - Prefix = ""; - - var nameAttr = source.GetCustomAttribute(); - if (nameAttr != null) - Name = nameAttr.Text; - - var summaryAttr = source.GetCustomAttribute(); - if (summaryAttr != null) - Summary = summaryAttr.Text; - - var remarksAttr = source.GetCustomAttribute(); - if (remarksAttr != null) - Remarks = remarksAttr.Text; - - List commands = new List(); - SearchClass(source, commands, Prefix); - Commands = commands; - - Preconditions = Source.GetCustomAttributes().ToImmutableArray(); - } - private void SearchClass(TypeInfo parentType, List commands, string groupPrefix) - { - foreach (var method in parentType.DeclaredMethods) - { - var cmdAttr = method.GetCustomAttribute(); - if (cmdAttr != null) - commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); - } - foreach (var type in parentType.DeclaredNestedTypes) - { - var groupAttrib = type.GetCustomAttribute(); - if (groupAttrib != null) - { - string nextGroupPrefix; - - if (groupPrefix != "") - nextGroupPrefix = groupPrefix + " " + (groupAttrib.Prefix ?? type.Name.ToLowerInvariant()); - else - nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); - - SearchClass(type, commands, nextGroupPrefix); - } - } - } - - internal ModuleBase CreateInstance(IDependencyMap map) - => _builder(map); - - public override string ToString() => Name; - private string DebuggerDisplay => Name; - } -} diff --git a/src/Discord.Net.Commands/ReflectionUtils.cs b/src/Discord.Net.Commands/ReflectionUtils.cs index 052e5fe98..27ea601bf 100644 --- a/src/Discord.Net.Commands/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/ReflectionUtils.cs @@ -18,7 +18,7 @@ namespace Discord.Commands throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); var constructor = constructors[0]; - ParameterInfo[] parameters = constructor.GetParameters(); + System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); return (map) => { From d25fbf78c061849130a5232ee804844e5df350b1 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Tue, 15 Nov 2016 21:46:16 +0000 Subject: [PATCH 33/88] Use lambda-style subbuilders --- .../Builders/CommandBuilder.cs | 18 ++++----- .../Builders/ModuleBuilder.cs | 38 ++++++------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 077787427..2ea01e3bb 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -10,18 +10,12 @@ namespace Discord.Commands.Builders private List parameters; private List aliases; - internal CommandBuilder(ModuleBuilder module, string prefix) + internal CommandBuilder(ModuleBuilder module) { preconditions = new List(); parameters = new List(); aliases = new List(); - if (prefix != null) - { - aliases.Add(prefix); - Name = prefix; - } - Module = module; } @@ -65,15 +59,17 @@ namespace Discord.Commands.Builders return this; } - public CommandBuilder AddParameter(ParameterBuilder parameter) + public CommandBuilder AddParameter(Action createFunc) { - parameters.Add(parameter); + var param = new ParameterBuilder(); + createFunc(param); + parameters.Add(param); return this; } - public CommandBuilder AddAlias(string alias) + public CommandBuilder AddAliases(params string[] newAliases) { - aliases.Add(alias); + aliases.AddRange(newAliases); return this; } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 0375383a8..dd30b0130 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -12,29 +12,15 @@ namespace Discord.Commands.Builders private List aliases; public ModuleBuilder() - : this(null, null) - { } - - public ModuleBuilder(string prefix) - : this(null, prefix) + : this(null) { } internal ModuleBuilder(ModuleBuilder parent) - : this(parent, null) - { } - - internal ModuleBuilder(ModuleBuilder parent, string prefix) { commands = new List(); submodules = new List(); preconditions = new List(); aliases = new List(); - - if (prefix != null) - { - aliases.Add(prefix); - Name = prefix; - } ParentModule = parent; } @@ -67,9 +53,9 @@ namespace Discord.Commands.Builders return this; } - public ModuleBuilder AddAlias(string alias) + public ModuleBuilder AddAliases(params string[] newAliases) { - aliases.Add(alias); + aliases.AddRange(newAliases); return this; } @@ -79,22 +65,20 @@ namespace Discord.Commands.Builders return this; } - public CommandBuilder AddCommand() => AddCommand(null); - public CommandBuilder AddCommand(string name) + public ModuleBuilder AddCommand(Action createFunc) { - var builder = new CommandBuilder(this, name); + var builder = new CommandBuilder(this); + createFunc(builder); commands.Add(builder); - - return builder; + return this; } - public ModuleBuilder AddSubmodule() => AddSubmodule(null); - public ModuleBuilder AddSubmodule(string prefix) + public ModuleBuilder AddSubmodule(Action createFunc) { - var builder = new ModuleBuilder(this, prefix); + var builder = new ModuleBuilder(this); + createFunc(builder); submodules.Add(builder); - - return builder; + return this; } public ModuleInfo Build(CommandService service) From ab95ced41da08f22c77c426ae730403602027228 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Tue, 15 Nov 2016 21:57:27 +0000 Subject: [PATCH 34/88] Use default TypeReader if not overriden --- src/Discord.Net.Commands/Builders/ParameterBuilder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index e90b2a9bf..8a0a17ab0 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -80,6 +80,9 @@ namespace Discord.Commands.Builders internal ParameterInfo Build(CommandInfo info, CommandService service) { + if (TypeReader == null) + TypeReader = service.GetTypeReader(ParameterType); + return new ParameterInfo(this, info, service); } } From 59d393b357d12de09b61830e86b378b640bcd60f Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Wed, 16 Nov 2016 11:43:23 -0500 Subject: [PATCH 35/88] Values in ImmutableDictionaries were strings rather than enumType --- src/Discord.Net.Commands/Readers/EnumTypeReader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index cd8b73896..ac18e9caf 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -31,13 +31,13 @@ namespace Discord.Commands var byNameBuilder = ImmutableDictionary.CreateBuilder(); var byValueBuilder = ImmutableDictionary.CreateBuilder(); - + foreach (var v in Enum.GetNames(_enumType)) - { - byNameBuilder.Add(v.ToLower(), v); - var parsedValue = (T)Enum.Parse(_enumType, v); - if (!byValueBuilder.ContainsKey(parsedValue)) - byValueBuilder.Add(parsedValue, v); + { + var parsedValue = Enum.Parse(_enumType, v); + byNameBuilder.Add(v.ToLower(), parsedValue); + if (!byValueBuilder.ContainsKey((T)parsedValue)) + byValueBuilder.Add((T)parsedValue, v); } _enumsByName = byNameBuilder.ToImmutable(); From 853f8f2e6b8810d4ad1e9a6f6dbde7a44d822389 Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 16 Nov 2016 07:36:29 -0400 Subject: [PATCH 36/88] Added prefixless TagHandling, added startIndex to resolve, fixed bugs. --- .../Entities/Messages/TagHandling.cs | 12 +++-- src/Discord.Net.Core/Utils/MentionUtils.cs | 54 +++++++++++++++---- .../Entities/Messages/RestUserMessage.cs | 5 +- .../Entities/Messages/RpcUserMessage.cs | 5 +- .../Entities/Messages/SocketUserMessage.cs | 5 +- 5 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs index 3572a37a5..492f05879 100644 --- a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs @@ -2,10 +2,12 @@ { public enum TagHandling { - Ignore = 0, - Remove, - Name, - FullName, - Sanitize + Ignore = 0, //<@53905483156684800> -> <@53905483156684800> + Remove, //<@53905483156684800> -> + Name, //<@53905483156684800> -> @Voltana + NameNoPrefix, //<@53905483156684800> -> Voltana + FullName, //<@53905483156684800> -> @Voltana#8252 + FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 + Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp) } } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 1b1408852..4d9add8fd 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -85,14 +85,17 @@ namespace Discord return false; } - internal static string Resolve(IMessage msg, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) + internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) { - var text = new StringBuilder(msg.Content); + var text = new StringBuilder(msg.Content.Substring(startIndex)); var tags = msg.Tags; - int indexOffset = 0; + int indexOffset = -startIndex; foreach (var tag in tags) { + if (tag.Index < startIndex) + continue; + string newText = ""; switch (tag.Type) { @@ -139,12 +142,22 @@ namespace Discord if (user != null) return $"@{guildUser?.Nickname ?? user?.Username}"; else - return $"@unknown-user"; + return $""; + case TagHandling.NameNoPrefix: + if (user != null) + return $"{guildUser?.Nickname ?? user?.Username}"; + else + return $""; case TagHandling.FullName: if (user != null) return $"@{user.Username}#{user.Discriminator}"; else - return $"@unknown-user"; + return $""; + case TagHandling.FullNameNoPrefix: + if (user != null) + return $"{user.Username}#{user.Discriminator}"; + else + return $""; case TagHandling.Sanitize: if (guildUser != null && guildUser.Nickname == null) return MentionUser($"{SanitizeChar}{tag.Key}", false); @@ -166,7 +179,13 @@ namespace Discord if (channel != null) return $"#{channel.Name}"; else - return $"#deleted-channel"; + return $""; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + if (channel != null) + return $"{channel.Name}"; + else + return $""; case TagHandling.Sanitize: return MentionChannel($"{SanitizeChar}{tag.Key}"); } @@ -185,7 +204,13 @@ namespace Discord if (role != null) return $"@{role.Name}"; else - return $"@deleted-role"; + return $""; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + if (role != null) + return $"{role.Name}"; + else + return $""; case TagHandling.Sanitize: return MentionRole($"{SanitizeChar}{tag.Key}"); } @@ -200,7 +225,9 @@ namespace Discord { case TagHandling.Name: case TagHandling.FullName: - return "@everyone"; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return "everyone"; case TagHandling.Sanitize: return $"@{SanitizeChar}everyone"; } @@ -215,9 +242,11 @@ namespace Discord { case TagHandling.Name: case TagHandling.FullName: - return "@everyone"; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return "here"; case TagHandling.Sanitize: - return $"@{SanitizeChar}everyone"; + return $"@{SanitizeChar}here"; } } return ""; @@ -232,8 +261,11 @@ namespace Discord case TagHandling.Name: case TagHandling.FullName: return $":{emoji.Name}:"; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return $"{emoji.Name}"; case TagHandling.Sanitize: - return $"<@{SanitizeChar}everyone"; + return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>"; } } return ""; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index d3d3b6fce..d3b5d77c6 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -122,9 +122,12 @@ namespace Discord.Rest public Task UnpinAsync(RequestOptions options) => MessageHelper.UnpinAsync(this, Discord, options); + public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; } diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs index edfa60484..3fc489ff9 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs @@ -106,9 +106,12 @@ namespace Discord.Rpc public Task UnpinAsync(RequestOptions options) => MessageHelper.UnpinAsync(this, Discord, options); + public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index db9b82ebd..2261076f7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -118,9 +118,12 @@ namespace Discord.WebSocket public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); + public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; From 228209aec8f3d4ba5c8e0cda157b2e8d9c4a5efe Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 16 Nov 2016 16:26:00 -0400 Subject: [PATCH 37/88] Ensure rate limits are updated when requests fail --- .../Net/Queue/RequestQueueBucket.cs | 94 +++++++++++-------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs index 19976e998..78a8e63cb 100644 --- a/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Core/Net/Queue/RequestQueueBucket.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.Net.Rest; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; #if DEBUG_LIMITS @@ -54,64 +55,79 @@ namespace Discord.Net.Queue #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sending..."); #endif - var response = await request.SendAsync().ConfigureAwait(false); - TimeSpan lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); - var info = new RateLimitInfo(response.Headers); - - if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) + TimeSpan lag = default(TimeSpan); + RateLimitInfo info = default(RateLimitInfo); + try { - switch (response.StatusCode) + var response = await request.SendAsync().ConfigureAwait(false); + lag = DateTimeOffset.UtcNow - DateTimeOffset.Parse(response.Headers["Date"]); + info = new RateLimitInfo(response.Headers); + + if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) { - case (HttpStatusCode)429: - if (info.IsGlobal) - { + switch (response.StatusCode) + { + case (HttpStatusCode)429: + if (info.IsGlobal) + { #if DEBUG_LIMITS - Debug.WriteLine($"[{id}] (!) 429 [Global]"); + Debug.WriteLine($"[{id}] (!) 429 [Global]"); #endif - _queue.PauseGlobal(info, lag); - } - else - { + _queue.PauseGlobal(info, lag); + } + else + { #if DEBUG_LIMITS - Debug.WriteLine($"[{id}] (!) 429"); + Debug.WriteLine($"[{id}] (!) 429"); #endif - UpdateRateLimit(id, request, info, lag, true); - } - await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); - continue; //Retry - case HttpStatusCode.BadGateway: //502 + UpdateRateLimit(id, request, info, lag, true); + } + await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); + continue; //Retry + case HttpStatusCode.BadGateway: //502 #if DEBUG_LIMITS - Debug.WriteLine($"[{id}] (!) 502"); + Debug.WriteLine($"[{id}] (!) 502"); #endif - continue; //Continue - default: - string reason = null; - if (response.Stream != null) - { - try + continue; //Continue + default: + string reason = null; + if (response.Stream != null) { - using (var reader = new StreamReader(response.Stream)) - using (var jsonReader = new JsonTextReader(reader)) + try { - var json = JToken.Load(jsonReader); - reason = json.Value("message"); + using (var reader = new StreamReader(response.Stream)) + using (var jsonReader = new JsonTextReader(reader)) + { + var json = JToken.Load(jsonReader); + reason = json.Value("message"); + } } + catch { } } - catch { } - } - throw new HttpException(response.StatusCode, reason); + throw new HttpException(response.StatusCode, reason); + } + } + else + { +#if DEBUG_LIMITS + Debug.WriteLine($"[{id}] Success"); +#endif + return response.Stream; } } - else - { #if DEBUG_LIMITS - Debug.WriteLine($"[{id}] Success"); + catch + { + Debug.WriteLine($"[{id}] Error"); + throw; + } #endif + finally + { UpdateRateLimit(id, request, info, lag, false); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Stop"); #endif - return response.Stream; } } } From 6d46347ebcf4cfdb1ecc74cdb43d4d83b2219593 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Wed, 16 Nov 2016 20:40:28 +0000 Subject: [PATCH 38/88] Finish implementation of command builders --- .../Builders/CommandBuilder.cs | 4 +- .../Builders/ModuleBuilder.cs | 10 +- .../Builders/ParameterBuilder.cs | 1 + src/Discord.Net.Commands/CommandService.cs | 50 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 17 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 13 +- .../Utilities/ModuleClassBuilder.cs | 213 ++++++++++++++++++ .../{ => Utilities}/ReflectionUtils.cs | 0 8 files changed, 255 insertions(+), 53 deletions(-) create mode 100644 src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs rename src/Discord.Net.Commands/{ => Utilities}/ReflectionUtils.cs (100%) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 2ea01e3bb..cd699ae61 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -22,7 +22,7 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public Func Callback { get; set; } + public Func Callback { get; set; } public ModuleBuilder Module { get; } public List Preconditions => preconditions; @@ -47,7 +47,7 @@ namespace Discord.Commands.Builders return this; } - public CommandBuilder SetCallback(Func callback) + public CommandBuilder SetCallback(Func callback) { Callback = callback; return this; diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index dd30b0130..79f818f38 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -12,16 +12,16 @@ namespace Discord.Commands.Builders private List aliases; public ModuleBuilder() - : this(null) - { } - - internal ModuleBuilder(ModuleBuilder parent) { commands = new List(); submodules = new List(); preconditions = new List(); aliases = new List(); - + } + + internal ModuleBuilder(ModuleBuilder parent) + : this() + { ParentModule = parent; } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 8a0a17ab0..7de26b72f 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -39,6 +39,7 @@ namespace Discord.Commands.Builders public ParameterBuilder SetDefault(T defaultValue) { + Optional = true; DefaultValue = defaultValue; ParameterType = typeof(T); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index ef0dba7e7..08cf75ac8 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -11,10 +11,8 @@ namespace Discord.Commands { public class CommandService { - private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); - private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _moduleDefs; + private readonly ConcurrentDictionary _moduleDefs; private readonly ConcurrentDictionary _typeReaders; private readonly CommandMap _map; @@ -71,16 +69,18 @@ namespace Discord.Commands try { var typeInfo = typeof(T).GetTypeInfo(); - if (!_moduleTypeInfo.IsAssignableFrom(typeInfo)) - throw new ArgumentException($"Modules must inherit ModuleBase."); - - if (typeInfo.IsAbstract) - throw new InvalidOperationException("Modules must not be abstract."); if (_moduleDefs.ContainsKey(typeof(T))) throw new ArgumentException($"This module has already been added."); - return AddModuleInternal(typeInfo); + var module = ModuleClassBuilder.Build(this, typeInfo).First(); + + _moduleDefs[typeof(T)] = module; + + foreach (var cmd in module.Commands) + _map.AddCommand(cmd); + + return module; } finally { @@ -93,43 +93,25 @@ namespace Discord.Commands await _moduleLock.WaitAsync().ConfigureAwait(false); try { - foreach (var type in assembly.ExportedTypes) - { - if (!_moduleDefs.ContainsKey(type)) - { - var typeInfo = type.GetTypeInfo(); - if (_moduleTypeInfo.IsAssignableFrom(typeInfo)) - { - var dontAutoLoad = typeInfo.GetCustomAttribute(); - if (dontAutoLoad == null && !typeInfo.IsAbstract) - moduleDefs.Add(AddModuleInternal(typeInfo)); - } - } - } - return moduleDefs.ToImmutable(); + var types = ModuleClassBuilder.Search(assembly); + return ModuleClassBuilder.Build(types, this).ToImmutableArray(); } finally { _moduleLock.Release(); } } - private ModuleInfo AddModuleInternal(TypeInfo typeInfo) - { - var moduleDef = new ModuleInfo(typeInfo, this); - _moduleDefs[typeInfo.AsType()] = moduleDef; - - foreach (var cmd in moduleDef.Commands) - _map.AddCommand(cmd); - - return moduleDef; - } public async Task RemoveModule(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - return RemoveModuleInternal(module.Source.BaseType); + var type = _moduleDefs.FirstOrDefault(x => x.Value == module); + if (default(KeyValuePair).Key == type.Key) + throw new KeyNotFoundException($"Could not find the key for the module {module?.Name ?? module?.Aliases?.FirstOrDefault()}"); + + return RemoveModuleInternal(type.Key); } finally { diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index fc1e0421b..31caaf3a8 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -15,7 +15,7 @@ namespace Discord.Commands private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); - private readonly Func _action; + private readonly Func _action; public ModuleInfo Module { get; } public string Name { get; } @@ -89,7 +89,7 @@ namespace Discord.Commands return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); } - public Task Execute(CommandContext context, ParseResult parseResult) + public Task Execute(CommandContext context, ParseResult parseResult, IDependencyMap map) { if (!parseResult.IsSuccess) return Task.FromResult(ExecuteResult.FromError(parseResult)); @@ -110,23 +110,26 @@ namespace Discord.Commands paramList[i] = parseResult.ParamValues[i].Values.First().Value; } - return Execute(context, argList, paramList); + return Execute(context, argList, paramList, map); } - public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList) + public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) { + if (map == null) + map = DependencyMap.Empty; + try { var args = GenerateArgs(argList, paramList); switch (RunMode) { case RunMode.Sync: //Always sync - await _action(context, args).ConfigureAwait(false); + await _action(context, args, map).ConfigureAwait(false); break; case RunMode.Mixed: //Sync until first await statement - var t1 = _action(context, args); + var t1 = _action(context, args, map); break; case RunMode.Async: //Always async - var t2 = Task.Run(() => _action(context, args)); + var t2 = Task.Run(() => _action(context, args, map)); break; } return ExecuteResult.FromSuccess(); diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 33a6574c4..6ec0d657b 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -31,14 +31,15 @@ namespace Discord.Commands Preconditions = BuildPreconditions(builder).ToImmutableArray(); } - private static List BuildAliases(ModuleBuilder builder) + private static IEnumerable BuildAliases(ModuleBuilder builder) { IEnumerable result = null; Stack builderStack = new Stack(); + builderStack.Push(builder); - ModuleBuilder parent = builder; - while (parent.ParentModule != null) + ModuleBuilder parent = builder.ParentModule; + while (parent != null) { builderStack.Push(parent); parent = parent.ParentModule; @@ -49,11 +50,13 @@ namespace Discord.Commands ModuleBuilder level = builderStack.Pop(); // get the topmost builder if (result == null) result = level.Aliases.ToList(); // create a shallow copy so we don't overwrite the builder unexpectedly - else + else if (result.Count() > level.Aliases.Count) result = result.Permutate(level.Aliases, (first, second) => first + " " + second); + else + result = level.Aliases.Permutate(result, (second, first) => first + " " + second); } - return result.ToList(); + return result; } private static List BuildPreconditions(ModuleBuilder builder) diff --git a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs new file mode 100644 index 000000000..30ee1e5ba --- /dev/null +++ b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs @@ -0,0 +1,213 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +using Discord.Commands.Builders; + +namespace Discord.Commands +{ + internal static class ModuleClassBuilder + { + private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); + + public static IEnumerable Search(Assembly assembly) + { + foreach (var type in assembly.ExportedTypes) + { + var typeInfo = type.GetTypeInfo(); + if (IsValidModuleDefinition(typeInfo) && + !typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) + { + yield return typeInfo; + } + } + } + + public static IEnumerable Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); + public static IEnumerable Build(IEnumerable validTypes, CommandService service) + { + if (!validTypes.Any()) + throw new InvalidOperationException("Could not find any valid modules from the given selection"); + + var topLevelGroups = validTypes.Where(x => x.DeclaringType == null); + var subGroups = validTypes.Intersect(topLevelGroups); + + var builtTypes = new List(); + + var result = new List(); + + foreach (var typeInfo in topLevelGroups) + { + // this shouldn't be the case; may be safe to remove? + if (builtTypes.Contains(typeInfo)) + continue; + + builtTypes.Add(typeInfo); + + var module = new ModuleBuilder(); + + BuildModule(module, typeInfo, service); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + + result.Add(module.Build(service)); + } + + return result; + } + + private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service) + { + foreach (var typeInfo in subTypes) + { + if (builtTypes.Contains(typeInfo)) + continue; + + builtTypes.Add(typeInfo); + + builder.AddSubmodule((module) => { + BuildModule(module, typeInfo, service); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + }); + } + } + + private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service) + { + var attributes = typeInfo.GetCustomAttributes(); + + foreach (var attribute in attributes) + { + // TODO: C#7 type switch + if (attribute is NameAttribute) + builder.Name = (attribute as NameAttribute).Text; + else if (attribute is SummaryAttribute) + builder.Summary = (attribute as SummaryAttribute).Text; + else if (attribute is RemarksAttribute) + builder.Remarks = (attribute as RemarksAttribute).Text; + else if (attribute is AliasAttribute) + builder.AddAliases((attribute as AliasAttribute).Aliases); + else if (attribute is GroupAttribute) + builder.AddAliases((attribute as GroupAttribute).Prefix); + else if (attribute is PreconditionAttribute) + builder.AddPrecondition(attribute as PreconditionAttribute); + } + + var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); + + foreach (var method in validCommands) + { + builder.AddCommand((command) => { + BuildCommand(command, typeInfo, method, service); + }); + } + } + + private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) + { + var attributes = method.GetCustomAttributes(); + + foreach (var attribute in attributes) + { + // TODO: C#7 type switch + if (attribute is NameAttribute) + builder.Name = (attribute as NameAttribute).Text; + else if (attribute is SummaryAttribute) + builder.Summary = (attribute as SummaryAttribute).Text; + else if (attribute is RemarksAttribute) + builder.Remarks = (attribute as RemarksAttribute).Text; + else if (attribute is AliasAttribute) + builder.AddAliases((attribute as AliasAttribute).Aliases); + else if (attribute is GroupAttribute) + builder.AddAliases((attribute as GroupAttribute).Prefix); + else if (attribute is PreconditionAttribute) + builder.AddPrecondition(attribute as PreconditionAttribute); + } + + var parameters = method.GetParameters(); + int pos = 0, count = parameters.Length; + foreach (var paramInfo in parameters) + { + builder.AddParameter((parameter) => { + BuildParameter(parameter, paramInfo, pos++, count, service); + }); + } + + var createInstance = ReflectionUtils.CreateBuilder(typeInfo, service); + + builder.Callback = (ctx, args, map) => { + var instance = createInstance(map); + instance.Context = ctx; + try + { + return method.Invoke(instance, args) as Task ?? Task.CompletedTask; + } + finally{ + (instance as IDisposable)?.Dispose(); + } + }; + } + + private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) + { + var attributes = paramInfo.GetCustomAttributes(); + var paramType = paramInfo.ParameterType; + + builder.Optional = paramInfo.IsOptional; + builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; + + foreach (var attribute in attributes) + { + // TODO: C#7 type switch + if (attribute is NameAttribute) + builder.Name = (attribute as NameAttribute).Text; + else if (attribute is SummaryAttribute) + builder.Summary = (attribute as SummaryAttribute).Text; + else if (attribute is ParamArrayAttribute) + { + builder.Multiple = true; + paramType = paramType.GetElementType(); + } + else if (attribute is RemainderAttribute) + { + if (position != count-1) + throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); + + builder.Remainder = true; + } + } + + var reader = service.GetTypeReader(paramType); + if (reader == null) + { + var paramTypeInfo = paramType.GetTypeInfo(); + if (paramTypeInfo.IsEnum) + { + reader = EnumTypeReader.GetReader(paramType); + service.AddTypeReader(paramType, reader); + } + else + { + throw new InvalidOperationException($"{paramType.FullName} is not supported as a command parameter, are you missing a TypeReader?"); + } + } + + builder.TypeReader = reader; + } + + private static bool IsValidModuleDefinition(TypeInfo typeInfo) + { + return _moduleTypeInfo.IsAssignableFrom(typeInfo) && + !typeInfo.IsAbstract; + } + + private static bool IsValidCommandDefinition(MethodInfo methodInfo) + { + return methodInfo.IsDefined(typeof(CommandAttribute)) && + methodInfo.ReturnType != typeof(Task) && + !methodInfo.IsStatic && + !methodInfo.IsGenericMethod; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Commands/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs similarity index 100% rename from src/Discord.Net.Commands/ReflectionUtils.cs rename to src/Discord.Net.Commands/Utilities/ReflectionUtils.cs From ef0337752e1a3660a67cea14f40962b04ed03b86 Mon Sep 17 00:00:00 2001 From: AntiTcb Date: Wed, 16 Nov 2016 23:16:37 -0500 Subject: [PATCH 39/88] Proper fix this time, fully tested on names and values. --- src/Discord.Net.Commands/Readers/EnumTypeReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index ac18e9caf..81870ecaf 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -37,7 +37,7 @@ namespace Discord.Commands var parsedValue = Enum.Parse(_enumType, v); byNameBuilder.Add(v.ToLower(), parsedValue); if (!byValueBuilder.ContainsKey((T)parsedValue)) - byValueBuilder.Add((T)parsedValue, v); + byValueBuilder.Add((T)parsedValue, parsedValue); } _enumsByName = byNameBuilder.ToImmutable(); From ddca1bc6a404941fa37f1c65e496f40ea4ebf062 Mon Sep 17 00:00:00 2001 From: ObsidianMinor Date: Thu, 17 Nov 2016 10:05:30 -0600 Subject: [PATCH 40/88] Migrated projects to Visual Studio 2017 --- Discord.Net.sln | 40 ++++++------ .../Discord.Net.Commands.csproj | 45 +++++++++++++ .../Discord.Net.Commands.xproj | 19 ------ src/Discord.Net.Commands/project.json | 43 ------------- src/Discord.Net.Core/Discord.Net.Core.csproj | 63 +++++++++++++++++++ src/Discord.Net.Core/Discord.Net.Core.xproj | 19 ------ src/Discord.Net.Core/project.json | 50 --------------- src/Discord.Net.Rest/Discord.Net.Rest.csproj | 48 ++++++++++++++ src/Discord.Net.Rest/Discord.Net.Rest.xproj | 19 ------ src/Discord.Net.Rest/project.json | 44 ------------- src/Discord.Net.Rpc/Discord.Net.Rpc.csproj | 52 +++++++++++++++ src/Discord.Net.Rpc/Discord.Net.Rpc.xproj | 19 ------ src/Discord.Net.Rpc/project.json | 48 -------------- .../Discord.Net.WebSocket.csproj | 62 ++++++++++++++++++ .../Discord.Net.WebSocket.xproj | 19 ------ src/Discord.Net.WebSocket/project.json | 55 ---------------- src/Discord.Net/Discord.Net.csproj | 46 ++++++++++++++ src/Discord.Net/Discord.Net.xproj | 19 ------ src/Discord.Net/project.json | 43 ------------- 19 files changed, 336 insertions(+), 417 deletions(-) create mode 100644 src/Discord.Net.Commands/Discord.Net.Commands.csproj delete mode 100644 src/Discord.Net.Commands/Discord.Net.Commands.xproj delete mode 100644 src/Discord.Net.Commands/project.json create mode 100644 src/Discord.Net.Core/Discord.Net.Core.csproj delete mode 100644 src/Discord.Net.Core/Discord.Net.Core.xproj delete mode 100644 src/Discord.Net.Core/project.json create mode 100644 src/Discord.Net.Rest/Discord.Net.Rest.csproj delete mode 100644 src/Discord.Net.Rest/Discord.Net.Rest.xproj delete mode 100644 src/Discord.Net.Rest/project.json create mode 100644 src/Discord.Net.Rpc/Discord.Net.Rpc.csproj delete mode 100644 src/Discord.Net.Rpc/Discord.Net.Rpc.xproj delete mode 100644 src/Discord.Net.Rpc/project.json create mode 100644 src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj delete mode 100644 src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj delete mode 100644 src/Discord.Net.WebSocket/project.json create mode 100644 src/Discord.Net/Discord.Net.csproj delete mode 100644 src/Discord.Net/Discord.Net.xproj delete mode 100644 src/Discord.Net/project.json diff --git a/Discord.Net.sln b/Discord.Net.sln index 48d0dbf93..8aaecee31 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.25914.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" ProjectSection(SolutionItems) = preProject @@ -9,19 +9,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net", "src\Discord.Net\Discord.Net.csproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.csproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.csproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,28 +31,28 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU - {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU - {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Debug|Any CPU {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Release|Any CPU + {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Release|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj new file mode 100644 index 000000000..2f137dbc7 --- /dev/null +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -0,0 +1,45 @@ + + + + + A Discord.Net extension adding support for bot commands. + 1.0.0-beta2 + netstandard1.3 + Discord.Net.Commands + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + + + $(DefineConstants);RELEASE + $(NoWarn);CS1573;CS1591 + true + true + + + + diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.xproj b/src/Discord.Net.Commands/Discord.Net.Commands.xproj deleted file mode 100644 index 597faf69c..000000000 --- a/src/Discord.Net.Commands/Discord.Net.Commands.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 078dd7e6-943d-4d09-afc2-d2ba58b76c9c - Discord.Commands - .\obj - .\bin\ - v4.5.2 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json deleted file mode 100644 index e730296a8..000000000 --- a/src/Discord.Net.Commands/project.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "A Discord.Net extension adding support for bot commands.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true, - "warningsAsErrors": true, - "xmlDoc": true - } - } - }, - - "dependencies": { - "Discord.Net.Core": { - "target": "project" - } - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj new file mode 100644 index 000000000..1c054c620 --- /dev/null +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -0,0 +1,63 @@ + + + + + A .Net API wrapper and bot framework for Discord. + 1.0.0-beta2 + netstandard1.3 + Discord.Net.Core + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + 4.0.1 + + + 9.0.1 + + + 4.0.12 + + + 1.2.0 + + + 3.0.0 + + + 4.1.0 + + + 4.0.0 + All + + + + + $(DefineConstants);RELEASE + $(NoWarn);CS1573;CS1591 + true + true + + + + diff --git a/src/Discord.Net.Core/Discord.Net.Core.xproj b/src/Discord.Net.Core/Discord.Net.Core.xproj deleted file mode 100644 index 6759e09b4..000000000 --- a/src/Discord.Net.Core/Discord.Net.Core.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 91e9e7bd-75c9-4e98-84aa-2c271922e5c2 - Discord - .\obj - .\bin\ - v4.5.2 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json deleted file mode 100644 index c44be49b6..000000000 --- a/src/Discord.Net.Core/project.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "A .Net API wrapper and bot framework for Discord.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true, - "warningsAsErrors": true, - "xmlDoc": true - } - } - }, - - "dependencies": { - "Microsoft.Win32.Primitives": "4.0.1", - "Newtonsoft.Json": "9.0.1", - "System.Collections.Concurrent": "4.0.12", - "System.Collections.Immutable": "1.2.0", - "System.Interactive.Async": "3.0.0", - "System.Net.Http": "4.1.0", - "System.Net.WebSockets.Client": { - "version": "4.0.0", - "type": "build" - } - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj new file mode 100644 index 000000000..0802a5975 --- /dev/null +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -0,0 +1,48 @@ + + + + + A core Discord.Net library containing the REST client and models. + 1.0.0-beta2 + netstandard1.3 + Discord.Net.Rest + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + 4.0.1 + + + + + $(DefineConstants);RELEASE + $(NoWarn);CS1573;CS1591 + true + true + + + + diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xproj b/src/Discord.Net.Rest/Discord.Net.Rest.xproj deleted file mode 100644 index 6a5d3e2b8..000000000 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - bfc6dc28-0351-4573-926a-d4124244c04f - Discord.Rest - .\obj - .\bin\ - v4.6.1 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json deleted file mode 100644 index 19b244961..000000000 --- a/src/Discord.Net.Rest/project.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "A core Discord.Net library containing the REST client and models.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true, - "warningsAsErrors": true, - "xmlDoc": true - } - } - }, - - "dependencies": { - "Discord.Net.Core": { - "target": "project" - }, - "System.IO.FileSystem": "4.0.1" - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj new file mode 100644 index 000000000..b676332b8 --- /dev/null +++ b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj @@ -0,0 +1,52 @@ + + + + + A core Discord.Net library containing the RPC client and models. + 1.0.0-beta2 + netstandard1.3 + Discord.Net.Rpc + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + 4.1.0 + + + 4.0.0 + + + + + $(DefineConstants);RELEASE + $(NoWarn);CS1573;CS1591 + true + true + + + + diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj deleted file mode 100644 index c5d036842..000000000 --- a/src/Discord.Net.Rpc/Discord.Net.Rpc.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 5688a353-121e-40a1-8bfa-b17b91fb48fb - Discord.Rpc - .\obj - .\bin\ - v4.6.1 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json deleted file mode 100644 index dc1944f5b..000000000 --- a/src/Discord.Net.Rpc/project.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "A core Discord.Net library containing the RPC client and models.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true, - "warningsAsErrors": true, - "xmlDoc": true - } - } - }, - - "dependencies": { - "Discord.Net.Core": { - "target": "project" - }, - "Discord.Net.Rest": { - "target": "project" - }, - "System.IO.Compression": "4.1.0", - "System.Net.WebSockets.Client": "4.0.0" - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj new file mode 100644 index 000000000..8019981ea --- /dev/null +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -0,0 +1,62 @@ + + + + + A core Discord.Net library containing the WebSocket client and models. + 1.0.0-beta2 + netstandard1.3 + true + Discord.Net.WebSocket + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + 4.1.0 + + + 4.0.0 + + + 4.1.0 + + + 4.0.0 + + + 4.1.0 + + + + + $(DefineConstants);RELEASE + $(NoWarn);CS1573;CS1591 + true + true + + + + diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj deleted file mode 100644 index 45e13b5ce..000000000 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 22ab6c66-536c-4ac2-bbdb-a8bc4eb6b14d - Discord.WebSocket - .\obj - .\bin\ - v4.6.1 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json deleted file mode 100644 index 7bc15a34d..000000000 --- a/src/Discord.Net.WebSocket/project.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "A core Discord.Net library containing the WebSocket client and models.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "buildOptions": { - "allowUnsafe": true - }, - - "configurations": { - "Release": { - "buildOptions": { - "define": [ "RELEASE" ], - "nowarn": [ "CS1573", "CS1591" ], - "optimize": true, - "warningsAsErrors": true, - "xmlDoc": true - } - } - }, - - "dependencies": { - "Discord.Net.Core": { - "target": "project" - }, - "Discord.Net.Rest": { - "target": "project" - }, - "System.IO.Compression": "4.1.0", - "System.Net.NameResolution": "4.0.0", - "System.Net.Sockets": "4.1.0", - "System.Net.WebSockets.Client": "4.0.0", - "System.Runtime.InteropServices": "4.1.0" - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj new file mode 100644 index 000000000..c3a7eb7f2 --- /dev/null +++ b/src/Discord.Net/Discord.Net.csproj @@ -0,0 +1,46 @@ + + + + + An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components. + 1.0.0-beta2 + netstandard1.3 + Discord.Net + discord;discordapp + https://github.com/RogueException/Discord.Net + http://opensource.org/licenses/MIT + git + git://github.com/RogueException/Discord.Net + $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.6.0 + + + + + $(DefineConstants);RELEASE + + + + diff --git a/src/Discord.Net/Discord.Net.xproj b/src/Discord.Net/Discord.Net.xproj deleted file mode 100644 index 079338b62..000000000 --- a/src/Discord.Net/Discord.Net.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 496db20a-a455-4d01-b6bc-90fe6d7c6b81 - Discord.Net - .\obj - .\bin\ - v4.6.1 - - - 2.0 - - - \ No newline at end of file diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json deleted file mode 100644 index 4286df5ea..000000000 --- a/src/Discord.Net/project.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "version": "1.0.0-beta2-*", - "description": "An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.", - "authors": [ "RogueException" ], - - "packOptions": { - "tags": [ "discord", "discordapp" ], - "licenseUrl": "http://opensource.org/licenses/MIT", - "projectUrl": "https://github.com/RogueException/Discord.Net", - "repository": { - "type": "git", - "url": "git://github.com/RogueException/Discord.Net" - } - }, - - "dependencies": { - "Discord.Net.Core": { - "target": "project" - }, - "Discord.Net.Rest": { - "target": "project" - }, - "Discord.Net.WebSocket": { - "target": "project" - }, - "Discord.Net.Rpc": { - "target": "project" - }, - "Discord.Net.Commands": { - "target": "project" - } - }, - - "frameworks": { - "netstandard1.3": { - "imports": [ - "dotnet5.4", - "dnxcore50", - "portable-net45+win8" - ] - } - } -} \ No newline at end of file From 0f674d24044a349b629e7c6691e92b47a57798dc Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 17:30:51 -0400 Subject: [PATCH 41/88] Updated dependencies to .NET Core 1.1 --- Discord.Net.sln | 66 +++++++++++++++++-- .../Discord.Net.Commands.csproj | 14 ++-- src/Discord.Net.Core/Discord.Net.Core.csproj | 25 +++---- src/Discord.Net.Rest/Discord.Net.Rest.csproj | 16 ++--- src/Discord.Net.Rpc/Discord.Net.Rpc.csproj | 18 ++--- .../Discord.Net.WebSocket.csproj | 24 +++---- src/Discord.Net/Discord.Net.csproj | 14 ++-- 7 files changed, 97 insertions(+), 80 deletions(-) diff --git a/Discord.Net.sln b/Discord.Net.sln index 8aaecee31..206c5b54a 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -17,49 +17,101 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.csproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.csproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{688FD1D8-7F01-4539-B2E9-F473C5D699C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x64.Build.0 = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|x86.Build.0 = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Debug|Any CPU {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x64.Build.0 = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.ActiveCfg = Debug|Any CPU + {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|x86.Build.0 = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.ActiveCfg = Debug|x64 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x64.Build.0 = Debug|x64 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.ActiveCfg = Debug|x86 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|x86.Build.0 = Debug|x86 {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Debug|Any CPU {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Debug|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.ActiveCfg = Release|x64 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x64.Build.0 = Release|x64 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.ActiveCfg = Release|x86 + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|x86.Build.0 = Release|x86 {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x64.Build.0 = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|x86.Build.0 = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Debug|Any CPU {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Debug|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x64.Build.0 = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.ActiveCfg = Debug|Any CPU + {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|x86.Build.0 = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x64.Build.0 = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|x86.Build.0 = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Debug|Any CPU {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.ActiveCfg = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x64.Build.0 = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.ActiveCfg = Debug|Any CPU + {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|x86.Build.0 = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x64.Build.0 = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|x86.Build.0 = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Debug|Any CPU {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x64.Build.0 = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|x86.Build.0 = Debug|Any CPU + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.ActiveCfg = Debug|x64 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x64.Build.0 = Debug|x64 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.ActiveCfg = Debug|x86 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Debug|x86.Build.0 = Debug|x86 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|Any CPU.Build.0 = Release|Any CPU + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.ActiveCfg = Release|x64 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x64.Build.0 = Release|x64 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.ActiveCfg = Release|x86 + {688FD1D8-7F01-4539-B2E9-F473C5D699C7}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} - {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D} = {288C363D-A636-4EAE-9AC1-4698B641B26E} {5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} + {688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E} EndGlobalSection EndGlobal diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj index 2f137dbc7..5cb1ddda0 100644 --- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -1,6 +1,5 @@  - A Discord.Net extension adding support for bot commands. 1.0.0-beta2 @@ -13,33 +12,28 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - + + False + $(DefineConstants);RELEASE $(NoWarn);CS1573;CS1591 true true - - + \ No newline at end of file diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 1c054c620..b93936b75 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,6 +1,5 @@  - A .Net API wrapper and bot framework for Discord. 1.0.0-beta2 @@ -13,51 +12,47 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - 4.0.1 + 4.3.0 9.0.1 - 4.0.12 + 4.3.0 - 1.2.0 + 1.3.0 - 3.0.0 + 3.1.0 - 4.1.0 + 4.3.0 - 4.0.0 + 4.3.0 All - + + False + $(DefineConstants);RELEASE $(NoWarn);CS1573;CS1591 true true - - + \ No newline at end of file diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 0802a5975..91396646f 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -1,6 +1,5 @@  - A core Discord.Net library containing the REST client and models. 1.0.0-beta2 @@ -13,36 +12,31 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - 4.0.1 + 4.3.0 - + + False + $(DefineConstants);RELEASE $(NoWarn);CS1573;CS1591 true true - - + \ No newline at end of file diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj index b676332b8..d85e2dcf8 100644 --- a/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj +++ b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj @@ -1,6 +1,5 @@  - A core Discord.Net library containing the RPC client and models. 1.0.0-beta2 @@ -13,40 +12,35 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - 4.1.0 + 4.3.0 - 4.0.0 + 4.3.0 - + + False + $(DefineConstants);RELEASE $(NoWarn);CS1573;CS1591 true true - - + \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index 8019981ea..293647294 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -1,6 +1,5 @@  - A core Discord.Net library containing the WebSocket client and models. 1.0.0-beta2 @@ -14,49 +13,44 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - 4.1.0 + 4.3.0 - 4.0.0 + 4.3.0 - 4.1.0 + 4.3.0 - 4.0.0 + 4.3.0 - 4.1.0 + 4.3.0 - + + False + $(DefineConstants);RELEASE $(NoWarn);CS1573;CS1591 true true - - + \ No newline at end of file diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index c3a7eb7f2..2d4c26f02 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -1,6 +1,5 @@  - An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components. 1.0.0-beta2 @@ -13,13 +12,11 @@ git://github.com/RogueException/Discord.Net $(PackageTargetFallback);dotnet5.4;dnxcore50;portable-net45+win8 - - @@ -27,20 +24,17 @@ - 1.0.0-alpha-20161104-2 All - - 1.6.0 - - + + False + $(DefineConstants);RELEASE - - + \ No newline at end of file From 6998e9262eca3e22d642cd9e1a17aa59fb43d4a5 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 18:17:05 -0400 Subject: [PATCH 42/88] Updated build script for .Net Core Preview 3 tooling --- build.bat | 13 +++++++------ global.json | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 global.json diff --git a/build.bat b/build.bat index 26df9c77a..bf72d74e9 100644 --- a/build.bat +++ b/build.bat @@ -1,8 +1,9 @@ @echo Off dotnet restore -dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" -dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" -dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" -dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" -dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" -dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" \ No newline at end of file +dotnet build +dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --no-build +dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --no-build +dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --no-build +dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --no-build +dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --no-build +dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --no-build \ No newline at end of file diff --git a/global.json b/global.json deleted file mode 100644 index 9a66d5edc..000000000 --- a/global.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "projects": [ "src", "test" ] -} From a8b399c8f6890d6c17d6643c0ddc4776a29d3b5e Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 14:34:15 -0400 Subject: [PATCH 43/88] Add version suffix to build script --- build.bat | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.bat b/build.bat index bf72d74e9..6432a3c5b 100644 --- a/build.bat +++ b/build.bat @@ -1,9 +1,9 @@ @echo Off dotnet restore dotnet build -dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --no-build -dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --no-build -dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --no-build -dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --no-build -dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --no-build -dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --no-build \ No newline at end of file +dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file From cf42bd1854b71cd9239722b2db0064067c7da1df Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:08:11 -0400 Subject: [PATCH 44/88] Restored project.json and old build script --- build.bat | 19 +++++-- .../Discord.Net.Commands.csproj | 2 + src/Discord.Net.Commands/project.json | 43 +++++++++++++++ src/Discord.Net.Core/Discord.Net.Core.csproj | 2 + src/Discord.Net.Core/project.json | 50 +++++++++++++++++ src/Discord.Net.Rest/Discord.Net.Rest.csproj | 2 + src/Discord.Net.Rest/project.json | 44 +++++++++++++++ src/Discord.Net.Rpc/Discord.Net.Rpc.csproj | 2 + src/Discord.Net.Rpc/project.json | 48 ++++++++++++++++ .../Discord.Net.WebSocket.csproj | 2 + src/Discord.Net.WebSocket/project.json | 55 +++++++++++++++++++ src/Discord.Net/Discord.Net.csproj | 2 + src/Discord.Net/project.json | 43 +++++++++++++++ 13 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 src/Discord.Net.Commands/project.json create mode 100644 src/Discord.Net.Core/project.json create mode 100644 src/Discord.Net.Rest/project.json create mode 100644 src/Discord.Net.Rpc/project.json create mode 100644 src/Discord.Net.WebSocket/project.json create mode 100644 src/Discord.Net/project.json diff --git a/build.bat b/build.bat index 6432a3c5b..3507ae277 100644 --- a/build.bat +++ b/build.bat @@ -1,9 +1,16 @@ @echo Off dotnet restore dotnet build -dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file +dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build + +REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj index 5cb1ddda0..63ffafd2c 100644 --- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -17,6 +17,7 @@ + @@ -26,6 +27,7 @@ All + False diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json new file mode 100644 index 000000000..9b59377c1 --- /dev/null +++ b/src/Discord.Net.Commands/project.json @@ -0,0 +1,43 @@ +{ + "version": "1.0.0-beta2-*", + "description": "A Discord.Net extension adding support for bot commands.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + } + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index b93936b75..b2d9fc870 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -17,6 +17,7 @@ + 1.0.0-alpha-20161104-2 @@ -45,6 +46,7 @@ All + False diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json new file mode 100644 index 000000000..09284718d --- /dev/null +++ b/src/Discord.Net.Core/project.json @@ -0,0 +1,50 @@ +{ + "version": "1.0.0-beta2-*", + "description": "A .Net API wrapper and bot framework for Discord.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Microsoft.Win32.Primitives": "4.3.0", + "Newtonsoft.Json": "9.0.1", + "System.Collections.Concurrent": "4.3.0", + "System.Collections.Immutable": "1.3.0", + "System.Interactive.Async": "3.1.0", + "System.Net.Http": "4.3.0", + "System.Net.WebSockets.Client": { + "version": "4.3.0", + "type": "build" + } + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 91396646f..6bc40571f 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -17,6 +17,7 @@ + @@ -29,6 +30,7 @@ 4.3.0 + False diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json new file mode 100644 index 000000000..4b0ff4431 --- /dev/null +++ b/src/Discord.Net.Rest/project.json @@ -0,0 +1,44 @@ +{ + "version": "1.0.0-beta2-*", + "description": "A core Discord.Net library containing the REST client and models.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "System.IO.FileSystem": "4.3.0" + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj index d85e2dcf8..efd2ea893 100644 --- a/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj +++ b/src/Discord.Net.Rpc/Discord.Net.Rpc.csproj @@ -17,6 +17,7 @@ + @@ -33,6 +34,7 @@ 4.3.0 + False diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json new file mode 100644 index 000000000..5c1be2d6c --- /dev/null +++ b/src/Discord.Net.Rpc/project.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0-beta2-*", + "description": "A core Discord.Net library containing the RPC client and models.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, + "System.IO.Compression": "4.3.0", + "System.Net.WebSockets.Client": "4.3.0" + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index 293647294..dd6541412 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -18,6 +18,7 @@ + @@ -43,6 +44,7 @@ 4.3.0 + False diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json new file mode 100644 index 000000000..790b95f4d --- /dev/null +++ b/src/Discord.Net.WebSocket/project.json @@ -0,0 +1,55 @@ +{ + "version": "1.0.0-beta2-*", + "description": "A core Discord.Net library containing the WebSocket client and models.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "buildOptions": { + "allowUnsafe": true + }, + + "configurations": { + "Release": { + "buildOptions": { + "define": [ "RELEASE" ], + "nowarn": [ "CS1573", "CS1591" ], + "optimize": true, + "warningsAsErrors": true, + "xmlDoc": true + } + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, + "System.IO.Compression": "4.3.0", + "System.Net.NameResolution": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.Net.WebSockets.Client": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index 2d4c26f02..ae9eb3e2a 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -17,6 +17,7 @@ + @@ -30,6 +31,7 @@ All + False diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json new file mode 100644 index 000000000..76ccbbd4b --- /dev/null +++ b/src/Discord.Net/project.json @@ -0,0 +1,43 @@ +{ + "version": "1.0.0-beta2-*", + "description": "An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "licenseUrl": "http://opensource.org/licenses/MIT", + "projectUrl": "https://github.com/RogueException/Discord.Net", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "dependencies": { + "Discord.Net.Core": { + "target": "project" + }, + "Discord.Net.Rest": { + "target": "project" + }, + "Discord.Net.WebSocket": { + "target": "project" + }, + "Discord.Net.Rpc": { + "target": "project" + }, + "Discord.Net.Commands": { + "target": "project" + } + }, + + "frameworks": { + "netstandard1.3": { + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + } + } +} \ No newline at end of file From b92747d8b739c44642f16db8f75ab2eb885d31c2 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:11:34 -0400 Subject: [PATCH 45/88] Use BuildCounter suffix in build script --- build.bat | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/build.bat b/build.bat index 3507ae277..7c7b90637 100644 --- a/build.bat +++ b/build.bat @@ -1,16 +1,16 @@ @echo Off dotnet restore dotnet build -dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file +REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build \ No newline at end of file From 42cbd67ee0a685cb49940dca64695d6a7cd3ea9c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:23:12 -0400 Subject: [PATCH 46/88] Removed prerelease tag from project.json --- build.bat | 12 ++++++------ src/Discord.Net.Commands/project.json | 2 +- src/Discord.Net.Core/project.json | 2 +- src/Discord.Net.Rest/project.json | 2 +- src/Discord.Net.Rpc/project.json | 2 +- src/Discord.Net.WebSocket/project.json | 2 +- src/Discord.Net/project.json | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.bat b/build.bat index 7c7b90637..e92def615 100644 --- a/build.bat +++ b/build.bat @@ -1,12 +1,12 @@ @echo Off dotnet restore dotnet build -dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build +dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build diff --git a/src/Discord.Net.Commands/project.json b/src/Discord.Net.Commands/project.json index 9b59377c1..a87591c03 100644 --- a/src/Discord.Net.Commands/project.json +++ b/src/Discord.Net.Commands/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "A Discord.Net extension adding support for bot commands.", "authors": [ "RogueException" ], diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json index 09284718d..6a62b9474 100644 --- a/src/Discord.Net.Core/project.json +++ b/src/Discord.Net.Core/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "A .Net API wrapper and bot framework for Discord.", "authors": [ "RogueException" ], diff --git a/src/Discord.Net.Rest/project.json b/src/Discord.Net.Rest/project.json index 4b0ff4431..b1ad15cb8 100644 --- a/src/Discord.Net.Rest/project.json +++ b/src/Discord.Net.Rest/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "A core Discord.Net library containing the REST client and models.", "authors": [ "RogueException" ], diff --git a/src/Discord.Net.Rpc/project.json b/src/Discord.Net.Rpc/project.json index 5c1be2d6c..8b2db7421 100644 --- a/src/Discord.Net.Rpc/project.json +++ b/src/Discord.Net.Rpc/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "A core Discord.Net library containing the RPC client and models.", "authors": [ "RogueException" ], diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json index 790b95f4d..46eb0eccd 100644 --- a/src/Discord.Net.WebSocket/project.json +++ b/src/Discord.Net.WebSocket/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "A core Discord.Net library containing the WebSocket client and models.", "authors": [ "RogueException" ], diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 76ccbbd4b..98b3b6211 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-beta2-*", + "version": "1.0.0-*", "description": "An aynchronous API wrapper for Discord. This metapackage includes all of the optional Discord.Net components.", "authors": [ "RogueException" ], From d21dd7d943b108fb72aa912fef533139a593ddb0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:25:56 -0400 Subject: [PATCH 47/88] Minor edit --- build.bat | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.bat b/build.bat index e92def615..3507ae277 100644 --- a/build.bat +++ b/build.bat @@ -8,9 +8,9 @@ dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build -REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%BuildCounter%" --no-build \ No newline at end of file +REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file From 675dab70f01c8f9c244f803d80ab4309a5f7084b Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:30:37 -0400 Subject: [PATCH 48/88] Update compiling and known issues in README --- README.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8bc4f4394..51d769c4c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Discord.Net v1.0.0-beta +# Discord.Net v1.0.0-beta2 [![MyGet](https://img.shields.io/myget/discord-net/vpre/Discord.Net.svg)](https://www.myget.org/feed/Packages/discord-net) [![MyGet Build Status](https://www.myget.org/BuildSource/Badge/discord-net?identifier=15bf7c42-22dd-4406-93e5-3cafc62bbc85)](https://www.myget.org/) [![Discord](https://discordapp.com/api/guilds/81384788765712384/widget.png)](https://discord.gg/0SBTUU1wZTYLhAAW) @@ -19,20 +19,14 @@ Bleeding edge builds are available using our MyGet feed (`https://www.myget.org/ ## Compiling In order to compile Discord.Net, you require the following: -### Using Visual Studio 2015 -- [VS2015 Update 3](https://www.microsoft.com/net/core#windows) -- [.Net Core 1.0 VS Plugin](https://www.microsoft.com/net/core#windows) +### Using Visual Studio 2017 +- [VS2017 RC](https://www.microsoft.com/net/core#windows) +- [.Net Core 1.1 VS Plugin](https://www.microsoft.com/net/core#windows) -### Using CLI -- [.Net Core 1.0 SDK](https://www.microsoft.com/net/core) +### Using Command Line +- [.Net Core 1.1 SDK](https://www.microsoft.com/net/core) ## Known Issues -### WebSockets -The current stable .Net Core websocket package does not support Linux, or pre-Win8. - -#### Linux -Add the latest version of `System.Net.WebSockets.Client` from the .Net Core MyGet feed (`https://dotnet.myget.org/F/dotnet-core/api/v3/index.json`) to your project. - -#### Windows 7 and earlier -There is currently no workaround, track the issue [here](https://github.com/dotnet/corefx/issues/9503). +### WebSockets (Win7 and earlier) +The .Net Core 1.1 does not support WebSockets on Win7 and earlier. Track the issue [here](https://github.com/dotnet/corefx/issues/9503). From f3a9abb95ad6b487418ac5231f6827b474f104cf Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:41:06 -0400 Subject: [PATCH 49/88] Improved compilation dependencies in README --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 51d769c4c..01f05d9ba 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ Bleeding edge builds are available using our MyGet feed (`https://www.myget.org/ ## Compiling In order to compile Discord.Net, you require the following: -### Using Visual Studio 2017 -- [VS2017 RC](https://www.microsoft.com/net/core#windows) -- [.Net Core 1.1 VS Plugin](https://www.microsoft.com/net/core#windows) +### Using Visual Studio +- [Visual Studio 2017 RC](https://www.microsoft.com/net/core#windowsvs2017) + +The .NET Core and Docker (Preview) workload is required during Visual Studio installation. ### Using Command Line -- [.Net Core 1.1 SDK](https://www.microsoft.com/net/core) +- [.Net Core 1.1 SDK](https://www.microsoft.com/net/download/core) ## Known Issues From c74a44d3ad593a9ed8235754645e7397c6bfdbdb Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 16:42:00 -0400 Subject: [PATCH 50/88] Minor edit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01f05d9ba..268a6d81d 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ The .NET Core and Docker (Preview) workload is required during Visual Studio ins ## Known Issues ### WebSockets (Win7 and earlier) -The .Net Core 1.1 does not support WebSockets on Win7 and earlier. Track the issue [here](https://github.com/dotnet/corefx/issues/9503). +.Net Core 1.1 does not support WebSockets on Win7 and earlier. Track the issue [here](https://github.com/dotnet/corefx/issues/9503). From f417c2b516132e03836803cafe80a79741c5dc3c Mon Sep 17 00:00:00 2001 From: RogueException Date: Thu, 17 Nov 2016 19:47:14 -0400 Subject: [PATCH 51/88] Removed no-build from build script --- build.bat | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/build.bat b/build.bat index 3507ae277..95a7c5d9a 100644 --- a/build.bat +++ b/build.bat @@ -1,16 +1,15 @@ @echo Off dotnet restore -dotnet build -dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build +dotnet pack "src\Discord.Net" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +dotnet pack "src\Discord.Net.Core" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +dotnet pack "src\Discord.Net.Commands" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +dotnet pack "src\Discord.Net.Rest" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +dotnet pack "src\Discord.Net.WebSocket" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +dotnet pack "src\Discord.Net.Rpc" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" -REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build -REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" --no-build \ No newline at end of file +REM dotnet pack "src\Discord.Net\Discord.Net.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +REM dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +REM dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +REM dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +REM dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" +REM dotnet pack "src\Discord.Net.Rpc\Discord.Net.Rpc.csproj" -c "%Configuration%" -o "artifacts" --version-suffix "%PrereleaseTag%" \ No newline at end of file From de645548a916f0c538e173c7531a3c250667ef47 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 18 Nov 2016 09:14:19 +0000 Subject: [PATCH 52/88] Complete command builders implementation In theory this should just work, more testing is needed though --- .../Builders/CommandBuilder.cs | 37 +++++++ .../Builders/ModuleBuilder.cs | 9 ++ .../Builders/ParameterBuilder.cs | 10 ++ src/Discord.Net.Commands/CommandService.cs | 100 +++++++++++++----- src/Discord.Net.Commands/Info/CommandInfo.cs | 4 + src/Discord.Net.Commands/Info/ModuleInfo.cs | 18 +++- .../Utilities/ModuleClassBuilder.cs | 42 +++++--- 7 files changed, 176 insertions(+), 44 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index cd699ae61..921e5df0a 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; @@ -22,6 +23,8 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } + public RunMode RunMode { get; set; } + public int Priority { get; set; } public Func Callback { get; set; } public ModuleBuilder Module { get; } @@ -47,6 +50,18 @@ namespace Discord.Commands.Builders return this; } + public CommandBuilder SetRunMode(RunMode runMode) + { + RunMode = runMode; + return this; + } + + public CommandBuilder SetPriority(int priority) + { + Priority = priority; + return this; + } + public CommandBuilder SetCallback(Func callback) { Callback = callback; @@ -75,6 +90,28 @@ namespace Discord.Commands.Builders internal CommandInfo Build(ModuleInfo info, CommandService service) { + if (aliases.Count == 0) + throw new InvalidOperationException("Commands require at least one alias to be registered"); + + if (Callback == null) + throw new InvalidOperationException("Commands require a callback to be built"); + + if (Name == null) + Name = aliases[0]; + + if (parameters.Count > 0) + { + var lastParam = parameters[parameters.Count - 1]; + + var firstMultipleParam = parameters.FirstOrDefault(x => x.Multiple); + if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) + throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); + + var firstRemainderParam = parameters.FirstOrDefault(x => x.Remainder); + if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) + throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); + } + return new CommandInfo(this, info, service); } } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 79f818f38..148eedfcf 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -83,6 +83,15 @@ namespace Discord.Commands.Builders public ModuleInfo Build(CommandService service) { + if (aliases.Count == 0) + throw new InvalidOperationException("Modules require at least one alias to be registered"); + + if (commands.Count == 0 && submodules.Count == 0) + throw new InvalidOperationException("Tried to build empty module"); + + if (Name == null) + Name = aliases[0]; + return new ModuleInfo(this, service); } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 7de26b72f..a6c8a5c23 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -81,9 +81,19 @@ namespace Discord.Commands.Builders internal ParameterInfo Build(CommandInfo info, CommandService service) { + // TODO: should we throw when we don't have a name? + if (Name == null) + Name = "[unknown parameter]"; + + if (ParameterType == null) + throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - An invalid parameter type was given"); + if (TypeReader == null) TypeReader = service.GetTypeReader(ParameterType); + if (TypeReader == null) + throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - A valid TypeReader could not be found"); + return new ParameterInfo(this, info, service); } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 08cf75ac8..ee18157dc 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -7,22 +7,26 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Discord.Commands.Builders; + namespace Discord.Commands { public class CommandService { private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _moduleDefs; + private readonly ConcurrentDictionary _typedModuleDefs; private readonly ConcurrentDictionary _typeReaders; + private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; - public IEnumerable Modules => _moduleDefs.Select(x => x.Value); - public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Value.Commands); + public IEnumerable Modules => _typedModuleDefs.Select(x => x.Value); + public IEnumerable Commands => _typedModuleDefs.SelectMany(x => x.Value.Commands); public CommandService() { _moduleLock = new SemaphoreSlim(1, 1); - _moduleDefs = new ConcurrentDictionary(); + _typedModuleDefs = new ConcurrentDictionary(); + _moduleDefs = new ConcurrentBag(); _map = new CommandMap(); _typeReaders = new ConcurrentDictionary { @@ -63,6 +67,22 @@ namespace Discord.Commands } //Modules + public async Task BuildModule(Action buildFunc) + { + await _moduleLock.WaitAsync().ConfigureAwait(false); + try + { + var builder = new ModuleBuilder(); + buildFunc(builder); + + var module = builder.Build(this); + return LoadModuleInternal(module); + } + finally + { + _moduleLock.Release(); + } + } public async Task AddModule() { await _moduleLock.WaitAsync().ConfigureAwait(false); @@ -70,17 +90,17 @@ namespace Discord.Commands { var typeInfo = typeof(T).GetTypeInfo(); - if (_moduleDefs.ContainsKey(typeof(T))) + if (_typedModuleDefs.ContainsKey(typeof(T))) throw new ArgumentException($"This module has already been added."); - var module = ModuleClassBuilder.Build(this, typeInfo).First(); + var module = ModuleClassBuilder.Build(this, typeInfo).FirstOrDefault(); - _moduleDefs[typeof(T)] = module; + if (module.Value == default(ModuleInfo)) + throw new InvalidOperationException($"Could not build the module {typeof(T).FullName}, did you pass an invalid type?"); - foreach (var cmd in module.Commands) - _map.AddCommand(cmd); - - return module; + _typedModuleDefs[module.Key] = module.Value; + + return LoadModuleInternal(module.Value); } finally { @@ -89,29 +109,44 @@ namespace Discord.Commands } public async Task> AddModules(Assembly assembly) { - var moduleDefs = ImmutableArray.CreateBuilder(); await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var types = ModuleClassBuilder.Search(assembly); - return ModuleClassBuilder.Build(types, this).ToImmutableArray(); + var types = ModuleClassBuilder.Search(assembly).ToArray(); + var moduleDefs = ModuleClassBuilder.Build(types, this); + + foreach (var info in moduleDefs) + { + _typedModuleDefs[info.Key] = info.Value; + LoadModuleInternal(info.Value); + } + + return moduleDefs.Select(x => x.Value).ToImmutableArray(); } finally { _moduleLock.Release(); } } + private ModuleInfo LoadModuleInternal(ModuleInfo module) + { + _moduleDefs.Add(module); + + foreach (var command in module.Commands) + _map.AddCommand(command); + + foreach (var submodule in module.Submodules) + LoadModuleInternal(submodule); + + return module; + } public async Task RemoveModule(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var type = _moduleDefs.FirstOrDefault(x => x.Value == module); - if (default(KeyValuePair).Key == type.Key) - throw new KeyNotFoundException($"Could not find the key for the module {module?.Name ?? module?.Aliases?.FirstOrDefault()}"); - - return RemoveModuleInternal(type.Key); + return RemoveModuleInternal(module); } finally { @@ -123,24 +158,33 @@ namespace Discord.Commands await _moduleLock.WaitAsync().ConfigureAwait(false); try { - return RemoveModuleInternal(typeof(T)); + ModuleInfo module; + _typedModuleDefs.TryGetValue(typeof(T), out module); + if (module == default(ModuleInfo)) + return false; + + return RemoveModuleInternal(module); } finally { _moduleLock.Release(); } } - private bool RemoveModuleInternal(Type type) + private bool RemoveModuleInternal(ModuleInfo module) { - ModuleInfo unloadedModule; - if (_moduleDefs.TryRemove(type, out unloadedModule)) + var defsRemove = module; + if (!_moduleDefs.TryTake(out defsRemove)) + return false; + + foreach (var cmd in module.Commands) + _map.RemoveCommand(cmd); + + foreach (var submodule in module.Submodules) { - foreach (var cmd in unloadedModule.Commands) - _map.RemoveCommand(cmd); - return true; + RemoveModuleInternal(submodule); } - else - return false; + + return true; } //Type Readers diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 31caaf3a8..8ece1cc2c 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -37,10 +37,14 @@ namespace Discord.Commands Summary = builder.Summary; Remarks = builder.Remarks; + RunMode = builder.RunMode; + Priority = builder.Priority; + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); Parameters = builder.Parameters.Select(x => x.Build(this, service)).ToImmutableArray(); + HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].Multiple : false; _action = builder.Callback; } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 6ec0d657b..f874f5540 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -17,6 +17,7 @@ namespace Discord.Commands public IReadOnlyList Aliases { get; } public IEnumerable Commands { get; } public IReadOnlyList Preconditions { get; } + public IReadOnlyList Submodules { get; } internal ModuleInfo(ModuleBuilder builder, CommandService service) { @@ -29,6 +30,8 @@ namespace Discord.Commands Aliases = BuildAliases(builder).ToImmutableArray(); Commands = builder.Commands.Select(x => x.Build(this, service)); Preconditions = BuildPreconditions(builder).ToImmutableArray(); + + Submodules = BuildSubmodules(builder, service).ToImmutableArray(); } private static IEnumerable BuildAliases(ModuleBuilder builder) @@ -59,13 +62,24 @@ namespace Discord.Commands return result; } + private static List BuildSubmodules(ModuleBuilder parent, CommandService service) + { + var result = new List(); + + foreach (var submodule in parent.Modules) + { + result.Add(submodule.Build(service)); + } + + return result; + } + private static List BuildPreconditions(ModuleBuilder builder) { var result = new List(); - ModuleBuilder parent = builder; - while (parent.ParentModule != null) + while (parent != null) { result.AddRange(parent.Preconditions); parent = parent.ParentModule; diff --git a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs index 30ee1e5ba..2280d9246 100644 --- a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs @@ -25,8 +25,8 @@ namespace Discord.Commands } } - public static IEnumerable Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); - public static IEnumerable Build(IEnumerable validTypes, CommandService service) + public static Dictionary Build(CommandService service, params TypeInfo[] validTypes) => Build(validTypes, service); + public static Dictionary Build(IEnumerable validTypes, CommandService service) { if (!validTypes.Any()) throw new InvalidOperationException("Could not find any valid modules from the given selection"); @@ -36,22 +36,20 @@ namespace Discord.Commands var builtTypes = new List(); - var result = new List(); + var result = new Dictionary(); foreach (var typeInfo in topLevelGroups) { // this shouldn't be the case; may be safe to remove? - if (builtTypes.Contains(typeInfo)) + if (result.ContainsKey(typeInfo.AsType())) continue; - builtTypes.Add(typeInfo); - var module = new ModuleBuilder(); BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); - result.Add(module.Build(service)); + result[typeInfo.AsType()] = module.Build(service); } return result; @@ -61,15 +59,18 @@ namespace Discord.Commands { foreach (var typeInfo in subTypes) { + if (!IsValidModuleDefinition(typeInfo)) + continue; + if (builtTypes.Contains(typeInfo)) continue; - - builtTypes.Add(typeInfo); builder.AddSubmodule((module) => { BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); }); + + builtTypes.Add(typeInfo); } } @@ -89,7 +90,10 @@ namespace Discord.Commands else if (attribute is AliasAttribute) builder.AddAliases((attribute as AliasAttribute).Aliases); else if (attribute is GroupAttribute) + { + builder.Name = builder.Name ?? (attribute as GroupAttribute).Prefix; builder.AddAliases((attribute as GroupAttribute).Prefix); + } else if (attribute is PreconditionAttribute) builder.AddPrecondition(attribute as PreconditionAttribute); } @@ -111,8 +115,17 @@ namespace Discord.Commands foreach (var attribute in attributes) { // TODO: C#7 type switch - if (attribute is NameAttribute) + if (attribute is CommandAttribute) + { + var cmdAttr = attribute as CommandAttribute; + builder.AddAliases(cmdAttr.Text); + builder.RunMode = cmdAttr.RunMode; + builder.Name = builder.Name ?? cmdAttr.Text; + } + else if (attribute is NameAttribute) builder.Name = (attribute as NameAttribute).Text; + else if (attribute is PriorityAttribute) + builder.Priority = (attribute as PriorityAttribute).Priority; else if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is RemarksAttribute) @@ -154,15 +167,15 @@ namespace Discord.Commands var attributes = paramInfo.GetCustomAttributes(); var paramType = paramInfo.ParameterType; + builder.Name = paramInfo.Name; + builder.Optional = paramInfo.IsOptional; builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; foreach (var attribute in attributes) { // TODO: C#7 type switch - if (attribute is NameAttribute) - builder.Name = (attribute as NameAttribute).Text; - else if (attribute is SummaryAttribute) + if (attribute is SummaryAttribute) builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is ParamArrayAttribute) { @@ -193,6 +206,7 @@ namespace Discord.Commands } } + builder.ParameterType = paramType; builder.TypeReader = reader; } @@ -205,7 +219,7 @@ namespace Discord.Commands private static bool IsValidCommandDefinition(MethodInfo methodInfo) { return methodInfo.IsDefined(typeof(CommandAttribute)) && - methodInfo.ReturnType != typeof(Task) && + methodInfo.ReturnType == typeof(Task) && !methodInfo.IsStatic && !methodInfo.IsGenericMethod; } From 196cce05afc0a784e7f40be0cfb0f7281a0ac9f7 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 18 Nov 2016 09:24:31 +0000 Subject: [PATCH 53/88] Clean up attribute checking and extraneous casts This probably isn't all of them. This is what I get for copy+pasting code. --- src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs index 2280d9246..85cd9a664 100644 --- a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs @@ -91,8 +91,9 @@ namespace Discord.Commands builder.AddAliases((attribute as AliasAttribute).Aliases); else if (attribute is GroupAttribute) { - builder.Name = builder.Name ?? (attribute as GroupAttribute).Prefix; - builder.AddAliases((attribute as GroupAttribute).Prefix); + var groupAttr = attribute as GroupAttribute; + builder.Name = builder.Name ?? groupAttr.Prefix; + builder.AddAliases(groupAttr.Prefix); } else if (attribute is PreconditionAttribute) builder.AddPrecondition(attribute as PreconditionAttribute); @@ -132,8 +133,6 @@ namespace Discord.Commands builder.Remarks = (attribute as RemarksAttribute).Text; else if (attribute is AliasAttribute) builder.AddAliases((attribute as AliasAttribute).Aliases); - else if (attribute is GroupAttribute) - builder.AddAliases((attribute as GroupAttribute).Prefix); else if (attribute is PreconditionAttribute) builder.AddPrecondition(attribute as PreconditionAttribute); } From 7505831998fca8ee28832b759912b1754d377098 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 06:36:51 -0400 Subject: [PATCH 54/88] Added custom messages to Preconditions --- src/Discord.Net.Core/Utils/Preconditions.cs | 256 +++++++++++--------- 1 file changed, 148 insertions(+), 108 deletions(-) diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 14c9db24d..92713368a 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -5,20 +5,20 @@ namespace Discord internal static class Preconditions { //Objects - public static void NotNull(T obj, string name) where T : class { if (obj == null) throw new ArgumentNullException(name); } - public static void NotNull(Optional obj, string name) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } + public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw new ArgumentNullException(name); } + public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } //Strings - public static void NotEmpty(string obj, string name) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - public static void NotEmpty(Optional obj, string name) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - public static void NotNullOrEmpty(string obj, string name) + public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + public static void NotNullOrEmpty(string obj, string name, string msg = null) { if (obj == null) throw new ArgumentNullException(name); if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - public static void NotNullOrEmpty(Optional obj, string name) + public static void NotNullOrEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { @@ -28,14 +28,14 @@ namespace Discord throw new ArgumentException("Argument cannot be empty.", name); } } - public static void NotNullOrWhitespace(string obj, string name) + public static void NotNullOrWhitespace(string obj, string name, string msg = null) { if (obj == null) throw new ArgumentNullException(name); if (obj.Trim().Length == 0) throw new ArgumentException("Argument cannot be blank.", name); } - public static void NotNullOrWhitespace(Optional obj, string name) + public static void NotNullOrWhitespace(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { @@ -47,105 +47,145 @@ namespace Discord } //Numerics - public static void NotEqual(sbyte obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(byte obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(short obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(ushort obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(int obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(uint obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(long obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(ulong obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(sbyte? obj, sbyte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(byte? obj, byte value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(short? obj, short value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(ushort? obj, ushort value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(int? obj, int value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(uint? obj, uint value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(long? obj, long value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(ulong? obj, ulong value, string name) { if (obj == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - public static void NotEqual(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value == value) throw new ArgumentOutOfRangeException(name); } - - public static void AtLeast(sbyte obj, sbyte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(byte obj, byte value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(short obj, short value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(ushort obj, ushort value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(int obj, int value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(uint obj, uint value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(long obj, long value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(ulong obj, ulong value, string name) { if (obj < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - public static void AtLeast(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value < value) throw new ArgumentOutOfRangeException(name); } - - public static void GreaterThan(sbyte obj, sbyte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(byte obj, byte value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(short obj, short value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(ushort obj, ushort value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(int obj, int value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(uint obj, uint value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(long obj, long value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(ulong obj, ulong value, string name) { if (obj <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - public static void GreaterThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value <= value) throw new ArgumentOutOfRangeException(name); } - - public static void AtMost(sbyte obj, sbyte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(byte obj, byte value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(short obj, short value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(ushort obj, ushort value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(int obj, int value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(uint obj, uint value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(long obj, long value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(ulong obj, ulong value, string name) { if (obj > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - public static void AtMost(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value > value) throw new ArgumentOutOfRangeException(name); } - - public static void LessThan(sbyte obj, sbyte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(byte obj, byte value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(short obj, short value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(ushort obj, ushort value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(int obj, int value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(uint obj, uint value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(long obj, long value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(ulong obj, ulong value, string name) { if (obj >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, sbyte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, byte value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, short value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, ushort value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, int value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, uint value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, long value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } - public static void LessThan(Optional obj, ulong value, string name) { if (obj.IsSpecified && obj.Value >= value) throw new ArgumentOutOfRangeException(name); } + public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(short obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(ushort obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(int obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(uint obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(long obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(ulong obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(sbyte? obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(byte? obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(short? obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(ushort? obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(int? obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(uint? obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(long? obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(ulong? obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + + private static ArgumentException CreateNotEqualException(string name, string msg, T value) + { + if (msg == null) + throw new ArgumentException($"Value may not be equal to {value}", name); + else + throw new ArgumentException(msg, name); + } + + public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(byte obj, byte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(short obj, short value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(ushort obj, ushort value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(int obj, int value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(uint obj, uint value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(long obj, long value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(ulong obj, ulong value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + public static void AtLeast(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + + private static ArgumentException CreateAtLeastException(string name, string msg, T value) + { + if (msg == null) + throw new ArgumentException($"Value must be at least {value}", name); + else + throw new ArgumentException(msg, name); + } + + public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(byte obj, byte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(short obj, short value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(ushort obj, ushort value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(int obj, int value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(uint obj, uint value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(long obj, long value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(ulong obj, ulong value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + public static void GreaterThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + + private static ArgumentException CreateGreaterThanException(string name, string msg, T value) + { + if (msg == null) + throw new ArgumentException($"Value must be greater than {value}", name); + else + throw new ArgumentException(msg, name); + } + + public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(byte obj, byte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(short obj, short value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(ushort obj, ushort value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(int obj, int value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(uint obj, uint value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(long obj, long value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(ulong obj, ulong value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + public static void AtMost(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + + private static ArgumentException CreateAtMostException(string name, string msg, T value) + { + if (msg == null) + throw new ArgumentException($"Value must be at most {value}", name); + else + throw new ArgumentException(msg, name); + } + + public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(byte obj, byte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(short obj, short value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(ushort obj, ushort value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(int obj, int value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(uint obj, uint value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(long obj, long value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(ulong obj, ulong value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + public static void LessThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + + private static ArgumentException CreateLessThanException(string name, string msg, T value) + { + if (msg == null) + throw new ArgumentException($"Value must be less than {value}", name); + else + throw new ArgumentException(msg, name); + } } } From 593ba46f1c0ade42e486a5a9a21107b694377798 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 07:28:30 -0400 Subject: [PATCH 55/88] Cleaned up command builders and async func names --- .../Builders/CommandBuilder.cs | 96 ++++++++-------- .../Builders/ModuleBuilder.cs | 105 ++++++++++-------- .../ModuleClassBuilder.cs | 19 ++-- .../Builders/ParameterBuilder.cs | 101 +++++++---------- src/Discord.Net.Commands/CommandService.cs | 22 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 22 ++-- src/Discord.Net.Commands/Info/ModuleInfo.cs | 7 +- .../Info/ParameterInfo.cs | 10 +- src/Discord.Net.Commands/Map/CommandMap.cs | 84 +++----------- .../Map/CommandMapNode.cs | 27 ++++- src/Discord.Net.Core/Utils/Preconditions.cs | 74 ++++++------ 11 files changed, 261 insertions(+), 306 deletions(-) rename src/Discord.Net.Commands/{Utilities => Builders}/ModuleClassBuilder.cs (93%) diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 921e5df0a..9b983fd1f 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -7,107 +7,109 @@ namespace Discord.Commands.Builders { public class CommandBuilder { - private List preconditions; - private List parameters; - private List aliases; + private readonly List _preconditions; + private readonly List _parameters; + private readonly List _aliases; - internal CommandBuilder(ModuleBuilder module) - { - preconditions = new List(); - parameters = new List(); - aliases = new List(); - - Module = module; - } + public ModuleBuilder Module { get; } + internal Func Callback { get; set; } public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } public RunMode RunMode { get; set; } public int Priority { get; set; } - public Func Callback { get; set; } - public ModuleBuilder Module { get; } - public List Preconditions => preconditions; - public List Parameters => parameters; - public List Aliases => aliases; + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Parameters => _parameters; + public IReadOnlyList Aliases => _aliases; + + //Automatic + internal CommandBuilder(ModuleBuilder module) + { + Module = module; + + _preconditions = new List(); + _parameters = new List(); + _aliases = new List(); + } + //User-defined + internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + : this(module) + { + Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); + Discord.Preconditions.NotNull(callback, nameof(callback)); + + Callback = callback; + _aliases.Add(primaryAlias); + } - public CommandBuilder SetName(string name) + public CommandBuilder WithName(string name) { Name = name; return this; } - - public CommandBuilder SetSummary(string summary) + public CommandBuilder WithSummary(string summary) { Summary = summary; return this; } - - public CommandBuilder SetRemarks(string remarks) + public CommandBuilder WithRemarks(string remarks) { Remarks = remarks; return this; } - - public CommandBuilder SetRunMode(RunMode runMode) + public CommandBuilder WithRunMode(RunMode runMode) { RunMode = runMode; return this; } - - public CommandBuilder SetPriority(int priority) + public CommandBuilder WithPriority(int priority) { Priority = priority; return this; } - public CommandBuilder SetCallback(Func callback) + public CommandBuilder AddAliases(params string[] aliases) { - Callback = callback; + _aliases.AddRange(aliases); return this; } - public CommandBuilder AddPrecondition(PreconditionAttribute precondition) { - preconditions.Add(precondition); + _preconditions.Add(precondition); return this; } - - public CommandBuilder AddParameter(Action createFunc) + public CommandBuilder AddParameter(string name, Type type, Action createFunc) { - var param = new ParameterBuilder(); + var param = new ParameterBuilder(this, name, type); createFunc(param); - parameters.Add(param); + _parameters.Add(param); return this; } - - public CommandBuilder AddAliases(params string[] newAliases) + internal CommandBuilder AddParameter(Action createFunc) { - aliases.AddRange(newAliases); + var param = new ParameterBuilder(this); + createFunc(param); + _parameters.Add(param); return this; } internal CommandInfo Build(ModuleInfo info, CommandService service) { - if (aliases.Count == 0) - throw new InvalidOperationException("Commands require at least one alias to be registered"); - - if (Callback == null) - throw new InvalidOperationException("Commands require a callback to be built"); - + //Default name to first alias if (Name == null) - Name = aliases[0]; + Name = _aliases[0]; - if (parameters.Count > 0) + if (_parameters.Count > 0) { - var lastParam = parameters[parameters.Count - 1]; + var lastParam = _parameters[_parameters.Count - 1]; - var firstMultipleParam = parameters.FirstOrDefault(x => x.Multiple); + var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) throw new InvalidOperationException("Only the last parameter in a command may have the Multiple flag."); - var firstRemainderParam = parameters.FirstOrDefault(x => x.Remainder); + var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) throw new InvalidOperationException("Only the last parameter in a command may have the Remainder flag."); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 148eedfcf..083de8e81 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -1,96 +1,107 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Threading.Tasks; namespace Discord.Commands.Builders { public class ModuleBuilder { - private List commands; - private List submodules; - private List preconditions; - private List aliases; - - public ModuleBuilder() - { - commands = new List(); - submodules = new List(); - preconditions = new List(); - aliases = new List(); - } - - internal ModuleBuilder(ModuleBuilder parent) - : this() - { - ParentModule = parent; - } + private readonly List _commands; + private readonly List _submodules; + private readonly List _preconditions; + private readonly List _aliases; + public CommandService Service { get; } + public ModuleBuilder Parent { get; } public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public ModuleBuilder ParentModule { get; } - public List Commands => commands; - public List Modules => submodules; - public List Preconditions => preconditions; - public List Aliases => aliases; + public IReadOnlyList Commands => _commands; + public IReadOnlyList Modules => _submodules; + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Aliases => _aliases; + + //Automatic + internal ModuleBuilder(CommandService service, ModuleBuilder parent) + { + Service = service; + Parent = parent; - public ModuleBuilder SetName(string name) + _commands = new List(); + _submodules = new List(); + _preconditions = new List(); + _aliases = new List(); + } + //User-defined + internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias) + : this(service, parent) + { + Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); + + _aliases = new List { primaryAlias }; + } + + public ModuleBuilder WithName(string name) { Name = name; return this; } - - public ModuleBuilder SetSummary(string summary) + public ModuleBuilder WithSummary(string summary) { Summary = summary; return this; } - - public ModuleBuilder SetRemarks(string remarks) + public ModuleBuilder WithRemarks(string remarks) { Remarks = remarks; return this; } - public ModuleBuilder AddAliases(params string[] newAliases) + public ModuleBuilder AddAlias(params string[] newAliases) { - aliases.AddRange(newAliases); + _aliases.AddRange(newAliases); return this; } - public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) { - preconditions.Add(precondition); + _preconditions.Add(precondition); return this; } - - public ModuleBuilder AddCommand(Action createFunc) + public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + { + var builder = new CommandBuilder(this, primaryAlias, callback); + createFunc(builder); + _commands.Add(builder); + return this; + } + internal ModuleBuilder AddCommand(Action createFunc) { var builder = new CommandBuilder(this); createFunc(builder); - commands.Add(builder); + _commands.Add(builder); return this; } - - public ModuleBuilder AddSubmodule(Action createFunc) + public ModuleBuilder AddModule(string primaryAlias, Action createFunc) { - var builder = new ModuleBuilder(this); + var builder = new ModuleBuilder(Service, this, primaryAlias); createFunc(builder); - submodules.Add(builder); + _submodules.Add(builder); + return this; + } + internal ModuleBuilder AddModule(Action createFunc) + { + var builder = new ModuleBuilder(Service, this); + createFunc(builder); + _submodules.Add(builder); return this; } public ModuleInfo Build(CommandService service) { - if (aliases.Count == 0) - throw new InvalidOperationException("Modules require at least one alias to be registered"); - - if (commands.Count == 0 && submodules.Count == 0) - throw new InvalidOperationException("Tried to build empty module"); - + //Default name to first alias if (Name == null) - Name = aliases[0]; + Name = _aliases[0]; return new ModuleInfo(this, service); } diff --git a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs similarity index 93% rename from src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs rename to src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 85cd9a664..9884f18db 100644 --- a/src/Discord.Net.Commands/Utilities/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -40,11 +40,11 @@ namespace Discord.Commands foreach (var typeInfo in topLevelGroups) { - // this shouldn't be the case; may be safe to remove? + // TODO: This shouldn't be the case; may be safe to remove? if (result.ContainsKey(typeInfo.AsType())) continue; - var module = new ModuleBuilder(); + var module = new ModuleBuilder(service, null); BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); @@ -65,7 +65,7 @@ namespace Discord.Commands if (builtTypes.Contains(typeInfo)) continue; - builder.AddSubmodule((module) => { + builder.AddModule((module) => { BuildModule(module, typeInfo, service); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); }); @@ -88,17 +88,20 @@ namespace Discord.Commands else if (attribute is RemarksAttribute) builder.Remarks = (attribute as RemarksAttribute).Text; else if (attribute is AliasAttribute) - builder.AddAliases((attribute as AliasAttribute).Aliases); + builder.AddAlias((attribute as AliasAttribute).Aliases); else if (attribute is GroupAttribute) { var groupAttr = attribute as GroupAttribute; builder.Name = builder.Name ?? groupAttr.Prefix; - builder.AddAliases(groupAttr.Prefix); + builder.AddAlias(groupAttr.Prefix); } else if (attribute is PreconditionAttribute) builder.AddPrecondition(attribute as PreconditionAttribute); } + if (builder.Name == null) + builder.Name = typeInfo.Name; + var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); foreach (var method in validCommands) @@ -168,7 +171,7 @@ namespace Discord.Commands builder.Name = paramInfo.Name; - builder.Optional = paramInfo.IsOptional; + builder.IsOptional = paramInfo.IsOptional; builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; foreach (var attribute in attributes) @@ -178,7 +181,7 @@ namespace Discord.Commands builder.Summary = (attribute as SummaryAttribute).Text; else if (attribute is ParamArrayAttribute) { - builder.Multiple = true; + builder.IsMultiple = true; paramType = paramType.GetElementType(); } else if (attribute is RemainderAttribute) @@ -186,7 +189,7 @@ namespace Discord.Commands if (position != count-1) throw new InvalidOperationException("Remainder parameters must be the last parameter in a command."); - builder.Remainder = true; + builder.IsRemainder = true; } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index a6c8a5c23..801a10080 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,100 +1,79 @@ using System; -using System.Threading.Tasks; -using System.Collections.Generic; +using System.Reflection; namespace Discord.Commands.Builders { public class ParameterBuilder { - public ParameterBuilder() - { } - - public ParameterBuilder(string name) - { - Name = name; - } - - public string Name { get; set; } - public string Summary { get; set; } - public object DefaultValue { get; set; } - public Type ParameterType { get; set; } + public CommandBuilder Command { get; } + public string Name { get; internal set; } + public Type ParameterType { get; internal set; } public TypeReader TypeReader { get; set; } + public bool IsOptional { get; set; } + public bool IsRemainder { get; set; } + public bool IsMultiple { get; set; } + public object DefaultValue { get; set; } + public string Summary { get; set; } - public bool Optional { get; set; } - public bool Remainder { get; set; } - public bool Multiple { get; set; } - - public ParameterBuilder SetName(string name) + //Automatic + internal ParameterBuilder(CommandBuilder command) { - Name = name; - return this; + Command = command; } - - public ParameterBuilder SetSummary(string summary) + //User-defined + internal ParameterBuilder(CommandBuilder command, string name, Type type) + : this(command) { - Summary = summary; - return this; + Preconditions.NotNull(name, nameof(name)); + + Name = name; + SetType(type); } - public ParameterBuilder SetDefault(T defaultValue) + internal void SetType(Type type) { - Optional = true; - DefaultValue = defaultValue; - ParameterType = typeof(T); + TypeReader = Command.Module.Service.GetTypeReader(type); - if (ParameterType.IsArray) - ParameterType = ParameterType.GetElementType(); - - return this; + if (type.GetTypeInfo().IsValueType) + DefaultValue = Activator.CreateInstance(type); + else if (type.IsArray) + type = ParameterType.GetElementType(); + ParameterType = type; } - - public ParameterBuilder SetType(Type parameterType) + + public ParameterBuilder WithSummary(string summary) { - ParameterType = parameterType; + Summary = summary; return this; } - - public ParameterBuilder SetTypeReader(TypeReader reader) + public ParameterBuilder WithDefault(object defaultValue) { - TypeReader = reader; + DefaultValue = defaultValue; return this; } - - public ParameterBuilder SetOptional(bool isOptional) + public ParameterBuilder WithIsOptional(bool isOptional) { - Optional = isOptional; + IsOptional = isOptional; return this; } - - public ParameterBuilder SetRemainder(bool isRemainder) + public ParameterBuilder WithIsRemainder(bool isRemainder) { - Remainder = isRemainder; + IsRemainder = isRemainder; return this; } - - public ParameterBuilder SetMultiple(bool isMultiple) + public ParameterBuilder WithIsMultiple(bool isMultiple) { - Multiple = isMultiple; + IsMultiple = isMultiple; return this; } - internal ParameterInfo Build(CommandInfo info, CommandService service) + internal ParameterInfo Build(CommandInfo info) { - // TODO: should we throw when we don't have a name? - if (Name == null) - Name = "[unknown parameter]"; - - if (ParameterType == null) - throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - An invalid parameter type was given"); - - if (TypeReader == null) - TypeReader = service.GetTypeReader(ParameterType); - if (TypeReader == null) - throw new InvalidOperationException($"Could not build parameter {Name} from command {info.Name} - A valid TypeReader could not be found"); + throw new InvalidOperationException($"No default TypeReader found, one must be specified"); - return new ParameterInfo(this, info, service); + return new ParameterInfo(this, info, Command.Module.Service); } } } \ No newline at end of file diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index ee18157dc..3c3760908 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -67,12 +67,12 @@ namespace Discord.Commands } //Modules - public async Task BuildModule(Action buildFunc) + public async Task CreateModuleAsync(string primaryAlias, Action buildFunc) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - var builder = new ModuleBuilder(); + var builder = new ModuleBuilder(this, null, primaryAlias); buildFunc(builder); var module = builder.Build(this); @@ -83,7 +83,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task AddModule() + public async Task AddModuleAsync() { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -107,7 +107,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModules(Assembly assembly) + public async Task> AddModulesAsync(Assembly assembly) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -141,7 +141,7 @@ namespace Discord.Commands return module; } - public async Task RemoveModule(ModuleInfo module) + public async Task RemoveModuleAsync(ModuleInfo module) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -153,7 +153,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task RemoveModule() + public async Task RemoveModuleAsync() { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -217,9 +217,9 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task Execute(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => Execute(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); - public async Task Execute(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(CommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); + public async Task ExecuteAsync(CommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { dependencyMap = dependencyMap ?? DependencyMap.Empty; @@ -230,7 +230,7 @@ namespace Discord.Commands var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { - var preconditionResult = await commands[i].CheckPreconditions(context, dependencyMap).ConfigureAwait(false); + var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false); if (!preconditionResult.IsSuccess) { if (commands.Count == 1) @@ -239,7 +239,7 @@ namespace Discord.Commands continue; } - var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false); + var parseResult = await commands[i].ParseAsync(context, searchResult, preconditionResult).ConfigureAwait(false); if (!parseResult.IsSuccess) { if (parseResult.Error == CommandError.MultipleMatches) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 8ece1cc2c..4d546b6fa 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -39,17 +39,21 @@ namespace Discord.Commands RunMode = builder.RunMode; Priority = builder.Priority; + + if (module.Aliases.Count != 0) + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); + else + Aliases = builder.Aliases.ToImmutableArray(); - Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); - Parameters = builder.Parameters.Select(x => x.Build(this, service)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].Multiple : false; + Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); + HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; _action = builder.Callback; } - public async Task CheckPreconditions(CommandContext context, IDependencyMap map = null) + public async Task CheckPreconditionsAsync(CommandContext context, IDependencyMap map = null) { if (map == null) map = DependencyMap.Empty; @@ -71,13 +75,13 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) + public async Task ParseAsync(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) { if (!searchResult.IsSuccess) return ParseResult.FromError(searchResult); if (preconditionResult != null && !preconditionResult.Value.IsSuccess) return ParseResult.FromError(preconditionResult.Value); - + string input = searchResult.Text; var matchingAliases = Aliases.Where(alias => input.StartsWith(alias)); @@ -114,9 +118,9 @@ namespace Discord.Commands paramList[i] = parseResult.ParamValues[i].Values.First().Value; } - return Execute(context, argList, paramList, map); + return ExecuteAsync(context, argList, paramList, map); } - public async Task Execute(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) + public async Task ExecuteAsync(CommandContext context, IEnumerable argList, IEnumerable paramList, IDependencyMap map) { if (map == null) map = DependencyMap.Empty; @@ -163,7 +167,7 @@ namespace Discord.Commands if (HasVarArgs) { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].ParameterType, t => + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => { var method = _convertParamsMethod.MakeGenericMethod(t); return (Func, object>)method.CreateDelegate(typeof(Func, object>)); diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index f874f5540..64fa29ea2 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; @@ -41,11 +40,11 @@ namespace Discord.Commands Stack builderStack = new Stack(); builderStack.Push(builder); - ModuleBuilder parent = builder.ParentModule; + ModuleBuilder parent = builder.Parent; while (parent != null) { builderStack.Push(parent); - parent = parent.ParentModule; + parent = parent.Parent; } while (builderStack.Count() > 0) @@ -82,7 +81,7 @@ namespace Discord.Commands while (parent != null) { result.AddRange(parent.Preconditions); - parent = parent.ParentModule; + parent = parent.Parent; } return result; diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 812fec572..18c5e653c 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -16,11 +16,11 @@ namespace Discord.Commands Name = builder.Name; Summary = builder.Summary; - IsOptional = builder.Optional; - IsRemainder = builder.Remainder; - IsMultiple = builder.Multiple; + IsOptional = builder.IsOptional; + IsRemainder = builder.IsRemainder; + IsMultiple = builder.IsMultiple; - ParameterType = builder.ParameterType; + Type = builder.ParameterType; DefaultValue = builder.DefaultValue; _reader = builder.TypeReader; @@ -32,7 +32,7 @@ namespace Discord.Commands public bool IsOptional { get; } public bool IsRemainder { get; } public bool IsMultiple { get; } - public Type ParameterType { get; } + public Type Type { get; } public object DefaultValue { get; } public async Task Parse(CommandContext context, string input) diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index e809c1b70..3a5239878 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -1,95 +1,39 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace Discord.Commands { internal class CommandMap { - static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; - private readonly object _lockObj = new object(); - - private readonly ConcurrentDictionary _nodes; + private readonly CommandMapNode _root; + private static readonly string[] _blankAliases = new[] { "" }; public CommandMap() { - _nodes = new ConcurrentDictionary(); + _root = new CommandMapNode(""); } public void AddCommand(CommandInfo command) { - foreach (string text in command.Aliases) - { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); - nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); - } - } + foreach (string text in GetAliases(command)) + _root.AddCommand(text, 0, command); } public void RemoveCommand(CommandInfo command) { - foreach (string text in command.Aliases) - { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) - { - nextNode.RemoveCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); - if (nextNode.IsEmpty) - _nodes.TryRemove(name, out nextNode); - } - } - } + foreach (string text in GetAliases(command)) + _root.RemoveCommand(text, 0, command); } public IEnumerable GetCommands(string text) { - int nextSpace = NextWhitespace(text); - string name; - - if (nextSpace == -1) - name = text; - else - name = text.Substring(0, nextSpace); - - lock (_lockObj) - { - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) - return nextNode.GetCommands(text, nextSpace + 1); - else - return Enumerable.Empty(); - } + return _root.GetCommands(text, 0); } - private static int NextWhitespace(string text) + private IReadOnlyList GetAliases(CommandInfo command) { - int lowest = int.MaxValue; - for (int i = 0; i < _whitespaceChars.Length; i++) - { - int index = text.IndexOf(_whitespaceChars[i]); - if (index != -1 && index < lowest) - lowest = index; - } - return (lowest != int.MaxValue) ? lowest : -1; + var aliases = command.Aliases; + if (aliases.Count == 0) + return _blankAliases; + return aliases; } } } diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index a348a8cb4..a86c0643d 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -6,6 +7,8 @@ namespace Discord.Commands { internal class CommandMapNode { + private static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; + private readonly ConcurrentDictionary _nodes; private readonly string _name; private readonly object _lockObj = new object(); @@ -22,13 +25,17 @@ namespace Discord.Commands public void AddCommand(string text, int index, CommandInfo command) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; lock (_lockObj) { if (text == "") + { + if (_name == "") + throw new InvalidOperationException("Cannot add commands to the root node."); _commands = _commands.Add(command); + } else { if (nextSpace == -1) @@ -43,7 +50,7 @@ namespace Discord.Commands } public void RemoveCommand(string text, int index, CommandInfo command) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; lock (_lockObj) @@ -70,7 +77,7 @@ namespace Discord.Commands public IEnumerable GetCommands(string text, int index) { - int nextSpace = text.IndexOf(' ', index); + int nextSpace = NextWhitespace(text, index); string name; var commands = _commands; @@ -92,5 +99,17 @@ namespace Discord.Commands } } } + + private static int NextWhitespace(string text, int startIndex) + { + int lowest = int.MaxValue; + for (int i = 0; i < _whitespaceChars.Length; i++) + { + int index = text.IndexOf(_whitespaceChars[i], startIndex); + if (index != -1 && index < lowest) + lowest = index; + } + return (lowest != int.MaxValue) ? lowest : -1; + } } } diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 92713368a..44d4e381b 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -5,47 +5,51 @@ namespace Discord internal static class Preconditions { //Objects - public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw new ArgumentNullException(name); } - public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw new ArgumentNullException(name); } + public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } + public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); } + + private static ArgumentNullException CreateNotNullException(string name, string msg) + { + if (msg == null) return new ArgumentNullException(name); + else return new ArgumentNullException(name, msg); + } //Strings - public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } - public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw new ArgumentException("Argument cannot be empty.", name); } + public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrEmpty(string obj, string name, string msg = null) { - if (obj == null) - throw new ArgumentNullException(name); - if (obj.Length == 0) - throw new ArgumentException("Argument cannot be empty.", name); + if (obj == null) throw CreateNotNullException(name, msg); + if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { - if (obj.Value == null) - throw new ArgumentNullException(name); - if (obj.Value.Length == 0) - throw new ArgumentException("Argument cannot be empty.", name); + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } } public static void NotNullOrWhitespace(string obj, string name, string msg = null) { - if (obj == null) - throw new ArgumentNullException(name); - if (obj.Trim().Length == 0) - throw new ArgumentException("Argument cannot be blank.", name); + if (obj == null) throw CreateNotNullException(name, msg); + if (obj.Trim().Length == 0) throw CreateNotEmptyException(name, msg); } public static void NotNullOrWhitespace(Optional obj, string name, string msg = null) { if (obj.IsSpecified) { - if (obj.Value == null) - throw new ArgumentNullException(name); - if (obj.Value.Trim().Length == 0) - throw new ArgumentException("Argument cannot be blank.", name); + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); } } + private static ArgumentException CreateNotEmptyException(string name, string msg) + { + if (msg == null) return new ArgumentException(name, "Argument cannot be blank."); + else return new ArgumentException(name, msg); + } + //Numerics public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } @@ -82,10 +86,8 @@ namespace Discord private static ArgumentException CreateNotEqualException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value may not be equal to {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); + else return new ArgumentException(msg, name); } public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } @@ -107,10 +109,8 @@ namespace Discord private static ArgumentException CreateAtLeastException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be at least {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be at least {value}", name); + else return new ArgumentException(msg, name); } public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } @@ -132,10 +132,8 @@ namespace Discord private static ArgumentException CreateGreaterThanException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be greater than {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); + else return new ArgumentException(msg, name); } public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } @@ -157,10 +155,8 @@ namespace Discord private static ArgumentException CreateAtMostException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be at most {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be at most {value}", name); + else return new ArgumentException(msg, name); } public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } @@ -182,10 +178,8 @@ namespace Discord private static ArgumentException CreateLessThanException(string name, string msg, T value) { - if (msg == null) - throw new ArgumentException($"Value must be less than {value}", name); - else - throw new ArgumentException(msg, name); + if (msg == null) return new ArgumentException($"Value must be less than {value}", name); + else return new ArgumentException(msg, name); } } } From 2c990f08f8fb02fa196e62e71b45f2cd9571f8db Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 08:33:34 -0400 Subject: [PATCH 56/88] Readded fluent-style to EmbedBuilder --- .../Entities/Messages/EmbedBuilder.cs | 181 ++++++++++++++++++ src/Discord.Net.Core/Utils/EmbedBuilder.cs | 82 -------- 2 files changed, 181 insertions(+), 82 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs delete mode 100644 src/Discord.Net.Core/Utils/EmbedBuilder.cs diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs new file mode 100644 index 000000000..5c0c72e8f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using Embed = Discord.API.Embed; +using Field = Discord.API.EmbedField; +using Author = Discord.API.EmbedAuthor; +using Footer = Discord.API.EmbedFooter; + +namespace Discord +{ + public class EmbedBuilder + { + private readonly Embed _model; + private readonly List _fields; + + public EmbedBuilder() + { + _model = new Embed(); + _model.Type = "rich"; + _fields = new List(); + } + + public string Title { get { return _model.Title; } set { _model.Title = value; } } + public string Description { get { return _model.Description; } set { _model.Description = value; } } + public string Url { get { return _model.Url; } set { _model.Url = value; } } + public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } + public EmbedAuthorBuilder Author { get; set; } + public EmbedFooterBuilder Footer { get; set; } + + public EmbedBuilder WithTitle(string title) + { + Title = title; + return this; + } + public EmbedBuilder WithDescription(string description) + { + Description = description; + return this; + } + public EmbedBuilder WithUrl(string url) + { + Url = url; + return this; + } + public EmbedBuilder WithColor(Color color) + { + Color = color; + return this; + } + + public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) + { + Author = author; + return this; + } + public EmbedBuilder WithAuthor(Action action) + { + var author = new EmbedAuthorBuilder(); + action(author); + Author = author; + return this; + } + public EmbedBuilder WithFooter(EmbedFooterBuilder footer) + { + Footer = footer; + return this; + } + public EmbedBuilder WithFooter(Action action) + { + var footer = new EmbedFooterBuilder(); + action(footer); + Footer = footer; + return this; + } + + public EmbedBuilder AddField(Action action) + { + var field = new EmbedFieldBuilder(); + action(field); + _fields.Add(field.ToModel()); + return this; + } + + internal Embed Build() + { + _model.Author = Author?.ToModel(); + _model.Footer = Footer?.ToModel(); + _model.Fields = _fields.ToArray(); + return _model; + } + } + + public class EmbedFieldBuilder + { + private Field _model; + + public string Name { get { return _model.Name; } set { _model.Name = value; } } + public string Value { get { return _model.Value; } set { _model.Value = value; } } + public bool IsInline { get { return _model.Inline; } set { _model.Inline = value; } } + + public EmbedFieldBuilder() + { + _model = new Field(); + } + + public EmbedFieldBuilder WithName(string name) + { + Name = name; + return this; + } + public EmbedFieldBuilder WithValue(string value) + { + Value = value; + return this; + } + public EmbedFieldBuilder WithIsInline(bool isInline) + { + IsInline = isInline; + return this; + } + + internal Field ToModel() => _model; + } + + public class EmbedAuthorBuilder + { + private Author _model; + + public string Name { get { return _model.Name; } set { _model.Name = value; } } + public string Url { get { return _model.Url; } set { _model.Url = value; } } + public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } } + + public EmbedAuthorBuilder() + { + _model = new Author(); + } + + public EmbedAuthorBuilder WithName(string name) + { + Name = name; + return this; + } + public EmbedAuthorBuilder WithUrl(string url) + { + Url = url; + return this; + } + public EmbedAuthorBuilder WithIconUrl(string iconUrl) + { + IconUrl = iconUrl; + return this; + } + + internal Author ToModel() => _model; + } + + public class EmbedFooterBuilder + { + private Footer _model; + + public string Text { get { return _model.Text; } set { _model.Text = value; } } + public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } } + + public EmbedFooterBuilder() + { + _model = new Footer(); + } + + public EmbedFooterBuilder WithText(string text) + { + Text = text; + return this; + } + public EmbedFooterBuilder WithIconUrl(string iconUrl) + { + IconUrl = iconUrl; + return this; + } + + internal Footer ToModel() => _model; + } +} diff --git a/src/Discord.Net.Core/Utils/EmbedBuilder.cs b/src/Discord.Net.Core/Utils/EmbedBuilder.cs deleted file mode 100644 index dca4377eb..000000000 --- a/src/Discord.Net.Core/Utils/EmbedBuilder.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using Embed = Discord.API.Embed; -using Field = Discord.API.EmbedField; -using Author = Discord.API.EmbedAuthor; -using Footer = Discord.API.EmbedFooter; - -namespace Discord -{ - public class EmbedBuilder - { - private Embed model = new Embed(); - private List fields = new List(); - - public EmbedBuilder() - { - model.Type = "rich"; - } - - public string Title { get { return model.Title; } set { model.Title = value; } } - public string Description { get { return model.Description; } set { model.Description = value; } } - public string Url { get { return model.Url; } set { model.Url = value; } } - public Color? Color { get { return model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null; } set { model.Color = value?.RawValue; } } - - public void SetAuthor(Action action) - { - var author = new EmbedBuilderAuthor(); - action(author); - model.Author = author.ToModel(); - } - public void SetFooter(Action action) - { - var footer = new EmbedBuilderFooter(); - action(footer); - model.Footer = footer.ToModel(); - } - public void AddField(Action action) - { - var field = new EmbedBuilderField(); - action(field); - fields.Add(field.ToModel()); - } - - internal Embed Build() - { - model.Fields = fields.ToArray(); - return model; - } - } - - public class EmbedBuilderField - { - private Field model = new Field(); - - public string Name { get { return model.Name; } set { model.Name = value; } } - public string Value { get { return model.Value; } set { model.Value = value; } } - public bool IsInline { get { return model.Inline; } set { model.Inline = value; } } - - internal Field ToModel() => model; - } - - public class EmbedBuilderAuthor - { - private Author model = new Author(); - - public string Name { get { return model.Name; } set { model.Name = value; } } - public string Url { get { return model.Url; } set { model.Url = value; } } - public string IconUrl { get { return model.IconUrl; } set { model.IconUrl = value; } } - - internal Author ToModel() => model; - } - - public class EmbedBuilderFooter - { - private Footer model = new Footer(); - - public string Text { get { return model.Text; } set { model.Text = value; } } - public string IconUrl { get { return model.IconUrl; } set { model.IconUrl = value; } } - - internal Footer ToModel() => model; - } -} From 3a5e4b23da213ee3e047b968b33bfe78337de6d0 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 08:59:33 -0400 Subject: [PATCH 57/88] Added IGuildUser.ChangeRolesAsync --- .../Extensions/GuildUserExtensions.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs index 57d6a13dc..855551c3d 100644 --- a/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/GuildUserExtensions.cs @@ -6,14 +6,23 @@ namespace Discord { public static class GuildUserExtensions { + //TODO: Should we remove Add/Remove? Encourages race conditions. public static Task AddRolesAsync(this IGuildUser user, params IRole[] roles) - => AddRolesAsync(user, (IEnumerable)roles); + => ChangeRolesAsync(user, add: roles); public static Task AddRolesAsync(this IGuildUser user, IEnumerable roles) - => user.ModifyAsync(x => x.RoleIds = user.RoleIds.Concat(roles.Select(y => y.Id)).ToArray()); - + => ChangeRolesAsync(user, add: roles); public static Task RemoveRolesAsync(this IGuildUser user, params IRole[] roles) - => RemoveRolesAsync(user, (IEnumerable)roles); + => ChangeRolesAsync(user, remove: roles); public static Task RemoveRolesAsync(this IGuildUser user, IEnumerable roles) - => user.ModifyAsync(x => x.RoleIds = user.RoleIds.Except(roles.Select(y => y.Id)).ToArray()); + => ChangeRolesAsync(user, remove: roles); + public static async Task ChangeRolesAsync(this IGuildUser user, IEnumerable add = null, IEnumerable remove = null) + { + IEnumerable roleIds = user.RoleIds; + if (remove != null) + roleIds = roleIds.Except(remove.Select(x => x.Id)); + if (add != null) + roleIds = roleIds.Concat(add.Select(x => x.Id)); + await user.ModifyAsync(x => x.RoleIds = roleIds.ToArray()).ConfigureAwait(false); + } } } From 53ee30c6487cfa062fe100cc9fee76fddcb0adcc Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 14:14:11 -0400 Subject: [PATCH 58/88] Virtual channels should not be exposed to socket classes. Dont store guild in messages. --- .../Entities/Channels/ChannelHelper.cs | 63 ++++++++++++------- .../Entities/Channels/RestDMChannel.cs | 16 ++--- .../Entities/Channels/RestGroupChannel.cs | 16 ++--- .../Entities/Channels/RestTextChannel.cs | 16 ++--- .../Channels/RestVirtualMessageChannel.cs | 16 ++--- .../Entities/Messages/RestMessage.cs | 12 ++-- .../Entities/Messages/RestSystemMessage.cs | 10 ++- .../Entities/Messages/RestUserMessage.cs | 15 +++-- .../Entities/Channels/RpcDMChannel.cs | 16 ++--- .../Entities/Channels/RpcGroupChannel.cs | 16 ++--- .../Entities/Channels/RpcTextChannel.cs | 16 ++--- .../Entities/Channels/SocketChannelHelper.cs | 8 +-- .../Entities/Channels/SocketDMChannel.cs | 22 +++---- .../Entities/Channels/SocketGroupChannel.cs | 22 +++---- .../Entities/Channels/SocketTextChannel.cs | 22 +++---- 15 files changed, 151 insertions(+), 135 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index e51a56370..1c2bdd476 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -43,13 +43,13 @@ namespace Discord.Rest } //Invites - public static async Task> GetInvitesAsync(IChannel channel, BaseDiscordClient client, + public static async Task> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray(); } - public static async Task CreateInviteAsync(IChannel channel, BaseDiscordClient client, + public static async Task CreateInviteAsync(IGuildChannel channel, BaseDiscordClient client, int? maxAge, int? maxUses, bool isTemporary, RequestOptions options) { var args = new CreateChannelInviteParams { IsTemporary = isTemporary }; @@ -62,18 +62,24 @@ namespace Discord.Rest } //Messages - public static async Task GetMessageAsync(IChannel channel, BaseDiscordClient client, - ulong id, IGuild guild, RequestOptions options) + public static async Task GetMessageAsync(IMessageChannel channel, BaseDiscordClient client, + ulong id, RequestOptions options) { + var guildId = (channel as IGuildChannel)?.GuildId; + var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); - return RestMessage.Create(client, guild, model); + var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + return RestMessage.Create(client, channel, author, model); } - public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client, - ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options) + public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + ulong? fromMessageId, Direction dir, int limit, RequestOptions options) { if (dir == Direction.Around) throw new NotImplementedException(); //TODO: Impl + var guildId = (channel as IGuildChannel)?.GuildId; + var guild = guildId != null ? (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; + return new PagedAsyncEnumerable( DiscordConfig.MaxMessagesPerBatch, async (info, ct) => @@ -85,8 +91,15 @@ namespace Discord.Rest }; if (info.Position != null) args.RelativeMessageId = info.Position.Value; + var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); - return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); + var builder = ImmutableArray.CreateBuilder(); + foreach (var model in models) + { + var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + builder.Add(RestMessage.Create(client, channel, author, model)); + } + return builder.ToImmutable(); }, nextPage: (info, lastPage) => { @@ -102,37 +115,45 @@ namespace Discord.Rest count: limit ); } - public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client, - IGuild guild, RequestOptions options) + public static async Task> GetPinnedMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + RequestOptions options) { + var guildId = (channel as IGuildChannel)?.GuildId; + var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); - return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); + var builder = ImmutableArray.CreateBuilder(); + foreach (var model in models) + { + var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + builder.Add(RestMessage.Create(client, channel, author, model)); + } + return builder.ToImmutable(); } - public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client, - string text, bool isTTS, EmbedBuilder embed, IGuild guild, RequestOptions options) + public static async Task SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, + string text, bool isTTS, EmbedBuilder embed, RequestOptions options) { var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.Build() }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); - return RestUserMessage.Create(client, guild, model); + return RestUserMessage.Create(client, channel, client.CurrentUser, model); } - public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, - string filePath, string text, bool isTTS, IGuild guild, RequestOptions options) + public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, + string filePath, string text, bool isTTS, RequestOptions options) { string filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return await SendFileAsync(channel, client, file, filename, text, isTTS, guild, options).ConfigureAwait(false); + return await SendFileAsync(channel, client, file, filename, text, isTTS, options).ConfigureAwait(false); } - public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client, - Stream stream, string filename, string text, bool isTTS, IGuild guild, RequestOptions options) + public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, + Stream stream, string filename, string text, bool isTTS, RequestOptions options) { var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); - return RestUserMessage.Create(client, guild, model); + return RestUserMessage.Create(client, channel, client.CurrentUser, model); } - public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client, + public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, IEnumerable messages, RequestOptions options) { var args = new DeleteMessagesParams(messages.Select(x => x.Id).ToArray()); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index cf5488a7e..573cfef72 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -53,22 +53,22 @@ namespace Discord.Rest } public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 1c7407f3b..e2a015c75 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -66,22 +66,22 @@ namespace Discord.Rest } public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index ad4c0037b..991b30283 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -45,22 +45,22 @@ namespace Discord.Rest => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs index 876447968..6127eaf65 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVirtualMessageChannel.cs @@ -23,22 +23,22 @@ namespace Discord.Rest } public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index ea064dd81..b50edf03b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -9,11 +9,10 @@ namespace Discord.Rest { public abstract class RestMessage : RestEntity, IMessage, IUpdateable { - internal readonly IGuild _guild; private long _timestampTicks; public IMessageChannel Channel { get; } - public RestUser Author { get; } + public IUser Author { get; } public string Content { get; private set; } @@ -32,19 +31,18 @@ namespace Discord.Rest public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) + internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) : base(discord, id) { Channel = channel; Author = author; - _guild = guild; } - internal static RestMessage Create(BaseDiscordClient discord, IGuild guild, Model model) + internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) { if (model.Type == MessageType.Default) - return RestUserMessage.Create(discord, guild, model); + return RestUserMessage.Create(discord, channel, author, model); else - return RestSystemMessage.Create(discord, guild, model); + return RestSystemMessage.Create(discord, channel, author, model); } internal virtual void Update(Model model) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 0725ab603..a5ced8c8f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,15 +8,13 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) - : base(discord, id, channel, author, guild) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) + : base(discord, id, channel, author) { } - internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model) + internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) { - var entity = new RestSystemMessage(discord, model.Id, - RestVirtualMessageChannel.Create(discord, model.ChannelId), - RestUser.Create(discord, model.Author.Value), guild); + var entity = new RestSystemMessage(discord, model.Id, channel, author); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index d3b5d77c6..de90448c7 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -30,15 +29,13 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); public override IReadOnlyCollection Tags => _tags; - internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) - : base(discord, id, channel, author, guild) + internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) + : base(discord, id, channel, author) { } - internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model) + internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) { - var entity = new RestUserMessage(discord, model.Id, - RestVirtualMessageChannel.Create(discord, model.ChannelId), - RestUser.Create(discord, model.Author.Value), guild); + var entity = new RestUserMessage(discord, model.Id, channel, author); entity.Update(model); return entity; } @@ -106,7 +103,9 @@ namespace Discord.Rest if (model.Content.IsSpecified) { var text = model.Content.Value; - _tags = MessageHelper.ParseTags(text, null, _guild, mentions); + var guildId = (Channel as IGuildChannel)?.GuildId; + var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; + _tags = MessageHelper.ParseTags(text, null, guild, mentions); model.Content = text; } } diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index 37e8de25b..af0102574 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -34,22 +34,22 @@ namespace Discord.Rpc //TODO: Use RPC cache public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index aae1a28f4..c88621d8f 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -36,22 +36,22 @@ namespace Discord.Rpc //TODO: Use RPC cache public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs index a3cc6bf3b..a5779bdbb 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs @@ -39,22 +39,22 @@ namespace Discord.Rpc //TODO: Use RPC cache public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, null, options); + => ChannelHelper.GetMessageAsync(this, Discord, id, options); public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options); + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 8b61e89e3..1bc0fc9b5 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -8,8 +8,8 @@ namespace Discord.WebSocket { internal static class SocketChannelHelper { - public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit, CacheMode mode, IGuild guild, RequestOptions options) + public static IAsyncEnumerable> GetMessagesAsync(ISocketMessageChannel channel, DiscordSocketClient discord, MessageCache messages, + ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (dir == Direction.Around) throw new NotImplementedException(); //TODO: Impl @@ -37,7 +37,7 @@ namespace Discord.WebSocket //Download remaining messages ulong? minId = cachedMessages.Count > 0 ? cachedMessages.Min(x => x.Id) : fromMessageId; - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, minId, dir, limit, guild, options); + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, minId, dir, limit, options); return result.Concat(downloadedMessages); } else @@ -46,7 +46,7 @@ namespace Discord.WebSocket return result; //Dont use cache in this case - return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, guild, options); + return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); } } public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 806f39254..48f87764b 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -48,15 +48,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options).ConfigureAwait(false); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -64,14 +64,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -123,11 +123,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index feb7103fe..93407e22e 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -71,15 +71,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options).ConfigureAwait(false); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -87,14 +87,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, null, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -186,11 +186,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 42b7ae373..9b687c9fb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -54,15 +54,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, Guild, options).ConfigureAwait(false); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -70,14 +70,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, Guild, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); public Task SendMessageAsync(string text, bool isTTS = false, EmbedBuilder embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, Guild, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); public Task SendFileAsync(string filePath, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, Guild, options); + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options); public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, Guild, options); + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -124,11 +124,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, Guild, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) From 9784a0324c7578c678510224d4c664b083f46766 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 18 Nov 2016 14:25:01 -0400 Subject: [PATCH 59/88] Create a RestUser for messages if a user isn't cached. --- .../Entities/Channels/ChannelHelper.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 1c2bdd476..99edc6f48 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; +using UserModel = Discord.API.User; namespace Discord.Rest { @@ -68,7 +69,7 @@ namespace Discord.Rest var guildId = (channel as IGuildChannel)?.GuildId; var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); - var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + var author = GetAuthor(client, guild, model.Author.Value); return RestMessage.Create(client, channel, author, model); } public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, @@ -96,7 +97,7 @@ namespace Discord.Rest var builder = ImmutableArray.CreateBuilder(); foreach (var model in models) { - var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + var author = GetAuthor(client, guild, model.Author.Value); builder.Add(RestMessage.Create(client, channel, author, model)); } return builder.ToImmutable(); @@ -124,7 +125,7 @@ namespace Discord.Rest var builder = ImmutableArray.CreateBuilder(); foreach (var model in models) { - var author = guild != null ? await guild.GetUserAsync(model.Author.Value.Id, CacheMode.CacheOnly).ConfigureAwait(false) : null; + var author = GetAuthor(client, guild, model.Author.Value); builder.Add(RestMessage.Create(client, channel, author, model)); } return builder.ToImmutable(); @@ -237,5 +238,16 @@ namespace Discord.Rest public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) => new TypingNotifier(client, channel, options); + + //Helpers + private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model) + { + IUser author = null; + if (guild != null) + author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result; + if (author == null) + author = RestUser.Create(client, model); + return author; + } } } From 8b8163fc2c240d85dcc8bbf7d1ab42a468ec8ea5 Mon Sep 17 00:00:00 2001 From: Aaron Scherer Date: Fri, 18 Nov 2016 15:16:42 -0800 Subject: [PATCH 60/88] Updating CDN url --- src/Discord.Net.Core/DiscordConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 0cb190726..b35f0d745 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -11,7 +11,7 @@ namespace Discord "Unknown"; public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; - public const string CDNUrl = "https://discordcdn.com/"; + public const string CDNUrl = "https://cdn.discordapp.com/"; public const string InviteUrl = "https://discord.gg/"; public const int DefaultRequestTimeout = 15000; From b0286975cdbd9f415cf3daf1ff60ed4e1e4b36b9 Mon Sep 17 00:00:00 2001 From: Davipb Date: Sat, 19 Nov 2016 10:56:17 -0200 Subject: [PATCH 61/88] Fix parsing of multiple quoted parameters This should Fix #262 --- src/Discord.Net.Commands/CommandParser.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 7bdbed955..53ea1330f 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -65,7 +65,9 @@ namespace Discord.Commands return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments."); else { - curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; + if (curParam == null) + curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; + if (curParam != null && curParam.IsRemainder) { argBuilder.Append(c); @@ -116,11 +118,7 @@ namespace Discord.Commands { paramList.Add(typeReaderResult); - if (curPos == endPos) - { - curParam = null; - curPart = ParserPart.None; - } + curPart = ParserPart.None; } else { From fd5e237b41427ba530bfbddd6b422deb183dde66 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sun, 20 Nov 2016 13:40:40 +0000 Subject: [PATCH 62/88] Fix commands from being created with invalid aliases --- .../Builders/ModuleClassBuilder.cs | 8 +++++++- src/Discord.Net.Commands/Info/CommandInfo.cs | 14 +++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 9884f18db..31694d1d1 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -116,6 +116,9 @@ namespace Discord.Commands { var attributes = method.GetCustomAttributes(); + builder.Name = method.Name; + + var setName = false; foreach (var attribute in attributes) { // TODO: C#7 type switch @@ -124,10 +127,13 @@ namespace Discord.Commands var cmdAttr = attribute as CommandAttribute; builder.AddAliases(cmdAttr.Text); builder.RunMode = cmdAttr.RunMode; - builder.Name = builder.Name ?? cmdAttr.Text; + builder.Name = setName ? builder.Name ?? cmdAttr.Text : cmdAttr.Text ?? builder.Name; } else if (attribute is NameAttribute) + { builder.Name = (attribute as NameAttribute).Text; + setName = true; + } else if (attribute is PriorityAttribute) builder.Priority = (attribute as PriorityAttribute).Priority; else if (attribute is SummaryAttribute) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 4d546b6fa..06b0f25a3 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -39,11 +39,19 @@ namespace Discord.Commands RunMode = builder.RunMode; Priority = builder.Priority; - - if (module.Aliases.Count != 0) + + // both command and module provide aliases + if (module.Aliases.Count > 0 && builder.Aliases.Count > 0) Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); - else + // only module provides aliases + else if (module.Aliases.Count > 0) + Aliases = module.Aliases.ToImmutableArray(); + // only command provides aliases + else if (builder.Aliases.Count > 0) Aliases = builder.Aliases.ToImmutableArray(); + // neither provide aliases + else + throw new InvalidOperationException("Cannot build a command without any aliases"); Preconditions = builder.Preconditions.ToImmutableArray(); From 05fb81c6176eb361e66a6db9946873fc26401417 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Sun, 20 Nov 2016 20:20:23 +0000 Subject: [PATCH 63/88] Fix a bunch of issues with aliases --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 7 ++++--- src/Discord.Net.Commands/CommandService.cs | 4 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 31694d1d1..57b40b6b3 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -118,7 +118,6 @@ namespace Discord.Commands builder.Name = method.Name; - var setName = false; foreach (var attribute in attributes) { // TODO: C#7 type switch @@ -127,12 +126,11 @@ namespace Discord.Commands var cmdAttr = attribute as CommandAttribute; builder.AddAliases(cmdAttr.Text); builder.RunMode = cmdAttr.RunMode; - builder.Name = setName ? builder.Name ?? cmdAttr.Text : cmdAttr.Text ?? builder.Name; + builder.Name = builder.Name ?? cmdAttr.Text; } else if (attribute is NameAttribute) { builder.Name = (attribute as NameAttribute).Text; - setName = true; } else if (attribute is PriorityAttribute) builder.Priority = (attribute as PriorityAttribute).Priority; @@ -146,6 +144,9 @@ namespace Discord.Commands builder.AddPrecondition(attribute as PreconditionAttribute); } + if (builder.Name == null) + builder.Name = method.Name; + var parameters = method.GetParameters(); int pos = 0, count = parameters.Length; foreach (var paramInfo in parameters) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 3c3760908..993dc7ebd 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -19,8 +19,8 @@ namespace Discord.Commands private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; - public IEnumerable Modules => _typedModuleDefs.Select(x => x.Value); - public IEnumerable Commands => _typedModuleDefs.SelectMany(x => x.Value.Commands); + public IEnumerable Modules => _moduleDefs.Select(x => x); + public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); public CommandService() { diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 06b0f25a3..55fdf0d90 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -42,7 +42,7 @@ namespace Discord.Commands // both command and module provide aliases if (module.Aliases.Count > 0 && builder.Aliases.Count > 0) - Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => first + " " + second).ToImmutableArray(); + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => second != null ? first + " " + second : first).ToImmutableArray(); // only module provides aliases else if (module.Aliases.Count > 0) Aliases = module.Aliases.ToImmutableArray(); From 09608b79c341cbae088ed8400d50ba96eef9261b Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Tue, 22 Nov 2016 22:22:16 +0100 Subject: [PATCH 64/88] Adds builders for Thumbnail and Image embeds, and adds json model for Video and Image embeds. --- src/Discord.Net.Core/API/Common/Embed.cs | 4 + src/Discord.Net.Core/API/Common/EmbedImage.cs | 17 ++++ src/Discord.Net.Core/API/Common/EmbedVideo.cs | 15 +++ .../Entities/Messages/EmbedBuilder.cs | 97 ++++++++++++++++++- .../Entities/Messages/EmbedImage.cs | 31 ++++++ .../Entities/Messages/EmbedVideo.cs | 29 ++++++ .../Entities/Messages/IEmbed.cs | 2 + .../Entities/Messages/Embed.cs | 8 ++ 8 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Core/API/Common/EmbedImage.cs create mode 100644 src/Discord.Net.Core/API/Common/EmbedVideo.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedImage.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs diff --git a/src/Discord.Net.Core/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs index ed4715237..eb56ce696 100644 --- a/src/Discord.Net.Core/API/Common/Embed.cs +++ b/src/Discord.Net.Core/API/Common/Embed.cs @@ -19,8 +19,12 @@ namespace Discord.API public Optional Author { get; set; } [JsonProperty("footer")] public Optional Footer { get; set; } + [JsonProperty("video")] + public Optional Video { get; set; } [JsonProperty("thumbnail")] public Optional Thumbnail { get; set; } + [JsonProperty("image")] + public Optional Image { get; set; } [JsonProperty("provider")] public Optional Provider { get; set; } [JsonProperty("fields")] diff --git a/src/Discord.Net.Core/API/Common/EmbedImage.cs b/src/Discord.Net.Core/API/Common/EmbedImage.cs new file mode 100644 index 000000000..ab4941ae0 --- /dev/null +++ b/src/Discord.Net.Core/API/Common/EmbedImage.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API +{ + public class EmbedImage + { + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("proxy_url")] + public string ProxyUrl { get; set; } + [JsonProperty("height")] + public Optional Height { get; set; } + [JsonProperty("width")] + public Optional Width { get; set; } + } +} diff --git a/src/Discord.Net.Core/API/Common/EmbedVideo.cs b/src/Discord.Net.Core/API/Common/EmbedVideo.cs new file mode 100644 index 000000000..072004631 --- /dev/null +++ b/src/Discord.Net.Core/API/Common/EmbedVideo.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 +using Newtonsoft.Json; + +namespace Discord.API +{ + public class EmbedVideo + { + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("height")] + public Optional Height { get; set; } + [JsonProperty("width")] + public Optional Width { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 5c0c72e8f..08c860d00 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -4,6 +4,8 @@ using Embed = Discord.API.Embed; using Field = Discord.API.EmbedField; using Author = Discord.API.EmbedAuthor; using Footer = Discord.API.EmbedFooter; +using Thumbnail = Discord.API.EmbedThumbnail; +using Image = Discord.API.EmbedImage; namespace Discord { @@ -14,8 +16,7 @@ namespace Discord public EmbedBuilder() { - _model = new Embed(); - _model.Type = "rich"; + _model = new Embed {Type = "rich"}; _fields = new List(); } @@ -25,6 +26,8 @@ namespace Discord public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } + public EmbedThumbnailBuilder Thumbnail { get; set; } + public EmbedImageBuilder Image { get; set; } public EmbedBuilder WithTitle(string title) { @@ -71,6 +74,30 @@ namespace Discord Footer = footer; return this; } + public EmbedBuilder WithThumbnail(EmbedThumbnailBuilder thumbnail) + { + Thumbnail = thumbnail; + return this; + } + public EmbedBuilder WithThumbnail(Action action) + { + var thumbnail = new EmbedThumbnailBuilder(); + action(thumbnail); + Thumbnail = thumbnail; + return this; + } + public EmbedBuilder WithImage(EmbedImageBuilder image) + { + Image = image; + return this; + } + public EmbedBuilder WithImage(Action action) + { + var image = new EmbedImageBuilder(); + action(image); + Image = image; + return this; + } public EmbedBuilder AddField(Action action) { @@ -84,6 +111,8 @@ namespace Discord { _model.Author = Author?.ToModel(); _model.Footer = Footer?.ToModel(); + _model.Thumbnail = Thumbnail?.ToModel(); + _model.Image = Image?.ToModel(); _model.Fields = _fields.ToArray(); return _model; } @@ -178,4 +207,68 @@ namespace Discord internal Footer ToModel() => _model; } + + public class EmbedThumbnailBuilder + { + private Thumbnail _model; + + public string Url { get { return _model.Url; } set { _model.Url = value; } } + public Optional Height { get { return _model.Height; } set { _model.Height = value; } } + public Optional Width { get { return _model.Width; } set { _model.Width = value; } } + + public EmbedThumbnailBuilder() + { + _model = new Thumbnail(); + } + + public EmbedThumbnailBuilder WithUrl(string url) + { + Url = url; + return this; + } + public EmbedThumbnailBuilder WithHeight(int height) + { + Height = height; + return this; + } + public EmbedThumbnailBuilder WithWidth(int width) + { + Width = width; + return this; + } + + internal Thumbnail ToModel() => _model; + } + + public class EmbedImageBuilder + { + private Image _model; + + public string Url { get { return _model.Url; } set { _model.Url = value; } } + public Optional Height { get { return _model.Height; } set { _model.Height = value; } } + public Optional Width { get { return _model.Width; } set { _model.Width = value; } } + + public EmbedImageBuilder() + { + _model = new Image(); + } + + public EmbedImageBuilder WithUrl(string url) + { + Url = url; + return this; + } + public EmbedImageBuilder WithHeight(int height) + { + Height = height; + return this; + } + public EmbedImageBuilder WithWidth(int width) + { + Width = width; + return this; + } + + internal Image ToModel() => _model; + } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs new file mode 100644 index 000000000..763d21c1f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using Model = Discord.API.EmbedImage; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public struct EmbedImage + { + public string Url { get; } + public string ProxyUrl { get; } + public int? Height { get; } + public int? Width { get; } + + private EmbedImage(string url, string proxyUrl, int? height, int? width) + { + Url = url; + ProxyUrl = proxyUrl; + Height = height; + Width = width; + } + internal static EmbedImage Create(Model model) + { + return new EmbedImage(model.Url, model.ProxyUrl, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + } + + private string DebuggerDisplay => $"{ToString()} ({Url})"; + public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs new file mode 100644 index 000000000..6d772252c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using Model = Discord.API.EmbedVideo; + +namespace Discord +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public struct EmbedVideo + { + public string Url { get; } + public int? Height { get; } + public int? Width { get; } + + private EmbedVideo(string url, int? height, int? width) + { + Url = url; + Height = height; + Width = width; + } + internal static EmbedVideo Create(Model model) + { + return new EmbedVideo(model.Url, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + } + + private string DebuggerDisplay => $"{ToString()} ({Url})"; + public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index c46b22d17..86463534d 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -9,6 +9,8 @@ namespace Discord string Title { get; } string Description { get; } Color? Color { get; } + EmbedImage? Image { get; } + EmbedVideo? Video { get; } EmbedAuthor? Author { get; } EmbedFooter? Footer { get; } EmbedProvider? Provider { get; } diff --git a/src/Discord.Net.Rest/Entities/Messages/Embed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs index 540a39ea2..7124df417 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -14,6 +14,8 @@ namespace Discord public string Title { get; } public string Type { get; } public Color? Color { get; } + public EmbedImage? Image { get; } + public EmbedVideo? Video { get; } public EmbedAuthor? Author { get; } public EmbedFooter? Footer { get; } public EmbedProvider? Provider { get; } @@ -25,6 +27,8 @@ namespace Discord string description, string url, Color? color, + EmbedImage? image, + EmbedVideo? video, EmbedAuthor? author, EmbedFooter? footer, EmbedProvider? provider, @@ -36,6 +40,8 @@ namespace Discord Description = description; Url = url; Color = color; + Image = image; + Video = video; Author = author; Footer = footer; Provider = provider; @@ -46,6 +52,8 @@ namespace Discord { return new Embed(model.Type, model.Title, model.Description, model.Url, model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, + model.Image.IsSpecified ? EmbedImage.Create(model.Image.Value) : (EmbedImage?)null, + model.Video.IsSpecified ? EmbedVideo.Create(model.Video.Value) : (EmbedVideo?)null, model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, From 6cdf5ee90ec5550f7a6b313d165a18ae4719c8f3 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Tue, 22 Nov 2016 23:38:43 +0100 Subject: [PATCH 65/88] Removed Height/Width. --- .../Entities/Messages/EmbedBuilder.cs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 08c860d00..c0069aa9a 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -16,7 +16,7 @@ namespace Discord public EmbedBuilder() { - _model = new Embed {Type = "rich"}; + _model = new Embed { Type = "rich" }; _fields = new List(); } @@ -213,8 +213,6 @@ namespace Discord private Thumbnail _model; public string Url { get { return _model.Url; } set { _model.Url = value; } } - public Optional Height { get { return _model.Height; } set { _model.Height = value; } } - public Optional Width { get { return _model.Width; } set { _model.Width = value; } } public EmbedThumbnailBuilder() { @@ -226,16 +224,6 @@ namespace Discord Url = url; return this; } - public EmbedThumbnailBuilder WithHeight(int height) - { - Height = height; - return this; - } - public EmbedThumbnailBuilder WithWidth(int width) - { - Width = width; - return this; - } internal Thumbnail ToModel() => _model; } @@ -245,8 +233,6 @@ namespace Discord private Image _model; public string Url { get { return _model.Url; } set { _model.Url = value; } } - public Optional Height { get { return _model.Height; } set { _model.Height = value; } } - public Optional Width { get { return _model.Width; } set { _model.Width = value; } } public EmbedImageBuilder() { @@ -258,16 +244,6 @@ namespace Discord Url = url; return this; } - public EmbedImageBuilder WithHeight(int height) - { - Height = height; - return this; - } - public EmbedImageBuilder WithWidth(int width) - { - Width = width; - return this; - } internal Image ToModel() => _model; } From f8e3ac9910656d68096e31dcc3ef2a94c6bffc51 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Wed, 23 Nov 2016 00:10:01 +0100 Subject: [PATCH 66/88] Something like this perhaps --- src/Discord.Net.Core/Entities/Messages/EmbedImage.cs | 4 ++-- src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs | 4 ++-- src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index 763d21c1f..47513dc19 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"{ToString()} ({Url})"; - public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; + private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 6a5fc4163..8acd2c134 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"{ToString()} ({Url})"; - public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; + private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index 6d772252c..8289d4813 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -23,7 +23,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"{ToString()} ({Url})"; - public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0"; + private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + public override string ToString() => Url; } } From 4cb79297356c1f2fea0298718df178de42b822b0 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Wed, 23 Nov 2016 00:27:33 +0100 Subject: [PATCH 67/88] Spacing --- src/Discord.Net.Core/Entities/Messages/EmbedImage.cs | 2 +- src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs | 2 +- src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index 47513dc19..76da598f1 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 8acd2c134..fe836d0f6 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index 8289d4813..18ee4ccfa 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -23,7 +23,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}){(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; public override string ToString() => Url; } } From 8cf0911a4be20502fe37e3dc071b3591c8ff84e4 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Wed, 23 Nov 2016 00:35:31 +0100 Subject: [PATCH 68/88] DebuggerDisplay fix. --- src/Discord.Net.Core/Entities/Messages/EmbedImage.cs | 2 +- src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs | 2 +- src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index 76da598f1..0b606c11e 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index fe836d0f6..dac2f5c7c 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -25,7 +25,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index 18ee4ccfa..3c93416d0 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -23,7 +23,7 @@ namespace Discord model.Width.IsSpecified ? model.Width.Value : (int?)null); } - private string DebuggerDisplay => $"({Url}) {(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")}"; + private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; public override string ToString() => Url; } } From a455ccc3343e058fcb0c6e3714128b33d1da4e49 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Thu, 24 Nov 2016 01:17:39 +0100 Subject: [PATCH 69/88] Adds a timestamp and fixes Volts issues with Thumbnail and Image URLs. --- src/Discord.Net.Core/API/Common/Embed.cs | 3 + .../Entities/Messages/EmbedBuilder.cs | 82 ++++--------------- .../Entities/Messages/IEmbed.cs | 4 +- .../Entities/Messages/Embed.cs | 8 +- 4 files changed, 26 insertions(+), 71 deletions(-) diff --git a/src/Discord.Net.Core/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs index eb56ce696..5a6661f0a 100644 --- a/src/Discord.Net.Core/API/Common/Embed.cs +++ b/src/Discord.Net.Core/API/Common/Embed.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System; using Newtonsoft.Json; namespace Discord.API @@ -15,6 +16,8 @@ namespace Discord.API public string Url { get; set; } [JsonProperty("color")] public uint? Color { get; set; } + [JsonProperty("timestamp")] + public Optional Timestamp { get; set; } [JsonProperty("author")] public Optional Author { get; set; } [JsonProperty("footer")] diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index c0069aa9a..f98371a31 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -23,11 +23,11 @@ namespace Discord public string Title { get { return _model.Title; } set { _model.Title = value; } } public string Description { get { return _model.Description; } set { _model.Description = value; } } public string Url { get { return _model.Url; } set { _model.Url = value; } } + public string ThumbnailUrl { get; set; } + public string ImageUrl { get; set; } public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } - public EmbedThumbnailBuilder Thumbnail { get; set; } - public EmbedImageBuilder Image { get; set; } public EmbedBuilder WithTitle(string title) { @@ -44,6 +44,16 @@ namespace Discord Url = url; return this; } + public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) + { + ThumbnailUrl = thumbnailUrl; + return this; + } + public EmbedBuilder WithImageUrl(string imageUrl) + { + ImageUrl = ImageUrl; + return this; + } public EmbedBuilder WithColor(Color color) { Color = color; @@ -74,30 +84,6 @@ namespace Discord Footer = footer; return this; } - public EmbedBuilder WithThumbnail(EmbedThumbnailBuilder thumbnail) - { - Thumbnail = thumbnail; - return this; - } - public EmbedBuilder WithThumbnail(Action action) - { - var thumbnail = new EmbedThumbnailBuilder(); - action(thumbnail); - Thumbnail = thumbnail; - return this; - } - public EmbedBuilder WithImage(EmbedImageBuilder image) - { - Image = image; - return this; - } - public EmbedBuilder WithImage(Action action) - { - var image = new EmbedImageBuilder(); - action(image); - Image = image; - return this; - } public EmbedBuilder AddField(Action action) { @@ -111,8 +97,8 @@ namespace Discord { _model.Author = Author?.ToModel(); _model.Footer = Footer?.ToModel(); - _model.Thumbnail = Thumbnail?.ToModel(); - _model.Image = Image?.ToModel(); + _model.Thumbnail = ThumbnailUrl != null ? new Thumbnail { Url = ThumbnailUrl } : null; + _model.Image = ImageUrl != null ? new Image { Url = ImageUrl } : null; _model.Fields = _fields.ToArray(); return _model; } @@ -207,44 +193,4 @@ namespace Discord internal Footer ToModel() => _model; } - - public class EmbedThumbnailBuilder - { - private Thumbnail _model; - - public string Url { get { return _model.Url; } set { _model.Url = value; } } - - public EmbedThumbnailBuilder() - { - _model = new Thumbnail(); - } - - public EmbedThumbnailBuilder WithUrl(string url) - { - Url = url; - return this; - } - - internal Thumbnail ToModel() => _model; - } - - public class EmbedImageBuilder - { - private Image _model; - - public string Url { get { return _model.Url; } set { _model.Url = value; } } - - public EmbedImageBuilder() - { - _model = new Image(); - } - - public EmbedImageBuilder WithUrl(string url) - { - Url = url; - return this; - } - - internal Image ToModel() => _model; - } } diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index 86463534d..d684827c3 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; namespace Discord { @@ -9,6 +10,7 @@ namespace Discord string Title { get; } string Description { get; } Color? Color { get; } + DateTimeOffset? Timestamp { get; } EmbedImage? Image { get; } EmbedVideo? Video { get; } EmbedAuthor? Author { get; } diff --git a/src/Discord.Net.Rest/Entities/Messages/Embed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs index 7124df417..f51e7e7a7 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -14,6 +14,7 @@ namespace Discord public string Title { get; } public string Type { get; } public Color? Color { get; } + public DateTimeOffset? Timestamp { get; } public EmbedImage? Image { get; } public EmbedVideo? Video { get; } public EmbedAuthor? Author { get; } @@ -26,7 +27,8 @@ namespace Discord string title, string description, string url, - Color? color, + Color? color, + DateTimeOffset? timestamp, EmbedImage? image, EmbedVideo? video, EmbedAuthor? author, @@ -40,6 +42,7 @@ namespace Discord Description = description; Url = url; Color = color; + Timestamp = timestamp; Image = image; Video = video; Author = author; @@ -52,13 +55,14 @@ namespace Discord { return new Embed(model.Type, model.Title, model.Description, model.Url, model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, + model.Timestamp.IsSpecified ? model.Timestamp.Value : (DateTimeOffset?)null, model.Image.IsSpecified ? EmbedImage.Create(model.Image.Value) : (EmbedImage?)null, model.Video.IsSpecified ? EmbedVideo.Create(model.Video.Value) : (EmbedVideo?)null, model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, model.Footer.IsSpecified ? EmbedFooter.Create(model.Footer.Value) : (EmbedFooter?)null, model.Provider.IsSpecified ? EmbedProvider.Create(model.Provider.Value) : (EmbedProvider?)null, model.Thumbnail.IsSpecified ? EmbedThumbnail.Create(model.Thumbnail.Value) : (EmbedThumbnail?)null, - model.Fields.IsSpecified ? model.Fields.Value.Select(x => EmbedField.Create(x)).ToImmutableArray() : ImmutableArray.Create()); + model.Fields.IsSpecified ? model.Fields.Value.Select(EmbedField.Create).ToImmutableArray() : ImmutableArray.Create()); } public override string ToString() => Title; From d0158816d373261f72458b63c2d415757bf2fa47 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Fri, 25 Nov 2016 17:09:17 +0100 Subject: [PATCH 70/88] Adds DateFormatString to DiscordRestApiClient --- src/Discord.Net.Core/API/Common/Embed.cs | 2 +- .../API/DiscordRestApiClient.cs | 2 +- .../Entities/Messages/EmbedBuilder.cs | 20 +++++++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/API/Common/Embed.cs b/src/Discord.Net.Core/API/Common/Embed.cs index 5a6661f0a..33b97e641 100644 --- a/src/Discord.Net.Core/API/Common/Embed.cs +++ b/src/Discord.Net.Core/API/Common/Embed.cs @@ -17,7 +17,7 @@ namespace Discord.API [JsonProperty("color")] public uint? Color { get; set; } [JsonProperty("timestamp")] - public Optional Timestamp { get; set; } + public DateTimeOffset? Timestamp { get; set; } [JsonProperty("author")] public Optional Author { get; set; } [JsonProperty("footer")] diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index d050f12a4..a03351a7b 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -49,7 +49,7 @@ namespace Discord.API { _restClientProvider = restClientProvider; _userAgent = userAgent; - _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + _serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", DateTimeZoneHandling = DateTimeZoneHandling.Utc, ContractResolver = new DiscordContractResolver() }; RequestQueue = requestQueue; FetchCurrentUser = true; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index f98371a31..045af3b05 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -20,12 +20,13 @@ namespace Discord _fields = new List(); } - public string Title { get { return _model.Title; } set { _model.Title = value; } } + public string Title { get { return _model.Title; } set { _model.Title = value; } } public string Description { get { return _model.Description; } set { _model.Description = value; } } public string Url { get { return _model.Url; } set { _model.Url = value; } } public string ThumbnailUrl { get; set; } public string ImageUrl { get; set; } public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } + public DateTimeOffset? Timestamp { get; set; } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } @@ -59,6 +60,16 @@ namespace Discord Color = color; return this; } + public EmbedBuilder WithTimestamp() + { + Timestamp = DateTimeOffset.UtcNow; + return this; + } + public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) + { + Timestamp = dateTimeOffset; + return this; + } public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) { @@ -97,6 +108,7 @@ namespace Discord { _model.Author = Author?.ToModel(); _model.Footer = Footer?.ToModel(); + _model.Timestamp = Timestamp?.ToUniversalTime(); _model.Thumbnail = ThumbnailUrl != null ? new Thumbnail { Url = ThumbnailUrl } : null; _model.Image = ImageUrl != null ? new Image { Url = ImageUrl } : null; _model.Fields = _fields.ToArray(); @@ -106,7 +118,7 @@ namespace Discord public class EmbedFieldBuilder { - private Field _model; + private readonly Field _model; public string Name { get { return _model.Name; } set { _model.Name = value; } } public string Value { get { return _model.Value; } set { _model.Value = value; } } @@ -138,7 +150,7 @@ namespace Discord public class EmbedAuthorBuilder { - private Author _model; + private readonly Author _model; public string Name { get { return _model.Name; } set { _model.Name = value; } } public string Url { get { return _model.Url; } set { _model.Url = value; } } @@ -170,7 +182,7 @@ namespace Discord public class EmbedFooterBuilder { - private Footer _model; + private readonly Footer _model; public string Text { get { return _model.Text; } set { _model.Text = value; } } public string IconUrl { get { return _model.IconUrl; } set { _model.IconUrl = value; } } From 54dd0a5cec3d67d7fed120d7bb07d532d3be6036 Mon Sep 17 00:00:00 2001 From: "Sindre G. Langhus" Date: Fri, 25 Nov 2016 21:51:01 +0100 Subject: [PATCH 71/88] Fixed ordering. --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- .../Entities/Messages/EmbedBuilder.cs | 12 ++++++------ src/Discord.Net.Core/Entities/Messages/IEmbed.cs | 2 +- src/Discord.Net.Rest/Entities/Messages/Embed.cs | 15 +++++++-------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index a03351a7b..451470d73 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -49,7 +49,7 @@ namespace Discord.API { _restClientProvider = restClientProvider; _userAgent = userAgent; - _serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", DateTimeZoneHandling = DateTimeZoneHandling.Utc, ContractResolver = new DiscordContractResolver() }; + _serializer = serializer ?? new JsonSerializer { DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", ContractResolver = new DiscordContractResolver() }; RequestQueue = requestQueue; FetchCurrentUser = true; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 045af3b05..aac361888 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -25,8 +25,8 @@ namespace Discord public string Url { get { return _model.Url; } set { _model.Url = value; } } public string ThumbnailUrl { get; set; } public string ImageUrl { get; set; } - public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } public DateTimeOffset? Timestamp { get; set; } + public Color? Color { get { return _model.Color.HasValue ? new Color(_model.Color.Value) : (Color?)null; } set { _model.Color = value?.RawValue; } } public EmbedAuthorBuilder Author { get; set; } public EmbedFooterBuilder Footer { get; set; } @@ -55,11 +55,6 @@ namespace Discord ImageUrl = ImageUrl; return this; } - public EmbedBuilder WithColor(Color color) - { - Color = color; - return this; - } public EmbedBuilder WithTimestamp() { Timestamp = DateTimeOffset.UtcNow; @@ -70,6 +65,11 @@ namespace Discord Timestamp = dateTimeOffset; return this; } + public EmbedBuilder WithColor(Color color) + { + Color = color; + return this; + } public EmbedBuilder WithAuthor(EmbedAuthorBuilder author) { diff --git a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs index d684827c3..5eef5ec9b 100644 --- a/src/Discord.Net.Core/Entities/Messages/IEmbed.cs +++ b/src/Discord.Net.Core/Entities/Messages/IEmbed.cs @@ -9,8 +9,8 @@ namespace Discord string Type { get; } string Title { get; } string Description { get; } - Color? Color { get; } DateTimeOffset? Timestamp { get; } + Color? Color { get; } EmbedImage? Image { get; } EmbedVideo? Video { get; } EmbedAuthor? Author { get; } diff --git a/src/Discord.Net.Rest/Entities/Messages/Embed.cs b/src/Discord.Net.Rest/Entities/Messages/Embed.cs index f51e7e7a7..a291dc6c0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Embed.cs @@ -13,8 +13,8 @@ namespace Discord public string Url { get; } public string Title { get; } public string Type { get; } - public Color? Color { get; } public DateTimeOffset? Timestamp { get; } + public Color? Color { get; } public EmbedImage? Image { get; } public EmbedVideo? Video { get; } public EmbedAuthor? Author { get; } @@ -24,11 +24,11 @@ namespace Discord public ImmutableArray Fields { get; } internal Embed(string type, - string title, - string description, - string url, - Color? color, + string title, + string description, + string url, DateTimeOffset? timestamp, + Color? color, EmbedImage? image, EmbedVideo? video, EmbedAuthor? author, @@ -53,9 +53,8 @@ namespace Discord } internal static Embed Create(Model model) { - return new Embed(model.Type, model.Title, model.Description, model.Url, - model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, - model.Timestamp.IsSpecified ? model.Timestamp.Value : (DateTimeOffset?)null, + return new Embed(model.Type, model.Title, model.Description, model.Url,model.Timestamp, + model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, model.Image.IsSpecified ? EmbedImage.Create(model.Image.Value) : (EmbedImage?)null, model.Video.IsSpecified ? EmbedVideo.Create(model.Video.Value) : (EmbedVideo?)null, model.Author.IsSpecified ? EmbedAuthor.Create(model.Author.Value) : (EmbedAuthor?)null, From bc76e38ce54a1d273f52e04c3ce78fe89e5a28bd Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 25 Nov 2016 21:15:39 +0000 Subject: [PATCH 72/88] Fix additional issue with aliases building incorrectly --- src/Discord.Net.Commands/Info/ModuleInfo.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 64fa29ea2..3baa2d34f 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -49,15 +49,21 @@ namespace Discord.Commands while (builderStack.Count() > 0) { - ModuleBuilder level = builderStack.Pop(); // get the topmost builder + ModuleBuilder level = builderStack.Pop(); //get the topmost builder if (result == null) - result = level.Aliases.ToList(); // create a shallow copy so we don't overwrite the builder unexpectedly + { + if (level.Aliases.Count > 0) + result = level.Aliases.ToList(); //create a shallow copy so we don't overwrite the builder unexpectedly + } else if (result.Count() > level.Aliases.Count) result = result.Permutate(level.Aliases, (first, second) => first + " " + second); else result = level.Aliases.Permutate(result, (second, first) => first + " " + second); } + if (result == null) //there were no aliases; default to an empty list + result = new List(); + return result; } From 5cdda592c19a6855ef761420235cd200f0164051 Mon Sep 17 00:00:00 2001 From: FiniteReality Date: Fri, 25 Nov 2016 21:19:41 +0000 Subject: [PATCH 73/88] Add requested changes --- src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 57b40b6b3..aaec43161 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -115,9 +115,7 @@ namespace Discord.Commands private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) { var attributes = method.GetCustomAttributes(); - - builder.Name = method.Name; - + foreach (var attribute in attributes) { // TODO: C#7 type switch @@ -129,9 +127,7 @@ namespace Discord.Commands builder.Name = builder.Name ?? cmdAttr.Text; } else if (attribute is NameAttribute) - { builder.Name = (attribute as NameAttribute).Text; - } else if (attribute is PriorityAttribute) builder.Priority = (attribute as PriorityAttribute).Priority; else if (attribute is SummaryAttribute) From fdecfe6bd4adcd0fc1d01948cf996ac0c8039eac Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 17:44:37 -0500 Subject: [PATCH 74/88] Add RequireOwner Precondition This precondition will require that the invoker of the command is the owner of the bot. --- .../Preconditions/RequireOwnerAttribute.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs new file mode 100644 index 000000000..e03e3971b --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Discord; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RequireOwnerAttribute : PreconditionAttribute + { + private IApplication application; + + public override async Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) + { + if (application == null) + application = await context.Client.GetApplicationInfoAsync(); + if (context.User.Id == application.Owner.Id) return PreconditionResult.FromSuccess(); + return PreconditionResult.FromError("Command can only be run by the owner of the bot"); + } + } +} From ea148db08b2c2fa643167a4d0703113ee04a44ab Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 17:48:08 -0500 Subject: [PATCH 75/88] Add RequireBotPermission, rename RequirePermission This is a breaking change. Adds a precondition that requires the bot has a specified permission (Resolves #211). Renames RequirePermission to RequireUserPermission. --- .../RequireBotPermissionAttribute.cs | 54 +++++++++++++++++++ ...e.cs => RequireUserPermissionAttribute.cs} | 6 +-- 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs rename src/Discord.Net.Commands/Attributes/Preconditions/{RequirePermissionAttribute.cs => RequireUserPermissionAttribute.cs} (89%) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs new file mode 100644 index 000000000..d3c598591 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RequireBotPermissionAttribute : PreconditionAttribute + { + public GuildPermission? GuildPermission { get; } + public ChannelPermission? ChannelPermission { get; } + + public RequireBotPermissionAttribute(GuildPermission permission) + { + GuildPermission = permission; + ChannelPermission = null; + } + public RequireBotPermissionAttribute(ChannelPermission permission) + { + ChannelPermission = permission; + GuildPermission = null; + } + + public override async Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) + { + var guildUser = await context.Guild.GetCurrentUserAsync(); + + if (GuildPermission.HasValue) + { + if (guildUser == null) + return PreconditionResult.FromError("Command must be used in a guild channel"); + if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) + return PreconditionResult.FromError($"Command requires guild permission {GuildPermission.Value}"); + } + + if (ChannelPermission.HasValue) + { + var guildChannel = context.Channel as IGuildChannel; + + ChannelPermissions perms; + if (guildChannel != null) + perms = guildUser.GetPermissions(guildChannel); + else + perms = ChannelPermissions.All(guildChannel); + + if (!perms.Has(ChannelPermission.Value)) + return PreconditionResult.FromError($"Command requires channel permission {ChannelPermission.Value}"); + } + + return PreconditionResult.FromSuccess(); + } + } +} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs similarity index 89% rename from src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs rename to src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 26aeac5ec..2e19b61cf 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -4,17 +4,17 @@ using System.Threading.Tasks; namespace Discord.Commands { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class RequirePermissionAttribute : PreconditionAttribute + public class RequireUserPermissionAttribute : PreconditionAttribute { public GuildPermission? GuildPermission { get; } public ChannelPermission? ChannelPermission { get; } - public RequirePermissionAttribute(GuildPermission permission) + public RequireUserPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } - public RequirePermissionAttribute(ChannelPermission permission) + public RequireUserPermissionAttribute(ChannelPermission permission) { ChannelPermission = permission; GuildPermission = null; From defc8f1c4eeee8302ec30549a021abfa5649beb0 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 18:09:18 -0500 Subject: [PATCH 76/88] Add docstrings to preconditions. --- .../RequireBotPermissionAttribute.cs | 21 ++++++++++++++++++ .../Preconditions/RequireContextAttribute.cs | 16 ++++++++++++++ .../Preconditions/RequireOwnerAttribute.cs | 4 ++++ .../RequireUserPermissionAttribute.cs | 22 +++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index d3c598591..914978192 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -5,17 +5,38 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// This attribute requires that the bot has a speicifed permission in the channel a command is invoked in. + /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireBotPermissionAttribute : PreconditionAttribute { public GuildPermission? GuildPermission { get; } public ChannelPermission? ChannelPermission { get; } + /// + /// Require that the bot account has a specified GuildPermission + /// + /// This precondition will always fail if the command is being invoked in a private channel. + /// The GuildPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together. public RequireBotPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } + /// + /// Require that the bot account has a specified ChannelPermission. + /// + /// The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing or ANDing the permissions together. + /// + /// + /// [Command("permission")] + /// [RequireBotPermission(ChannelPermission.ManageMessages)] + /// public async Task Purge() + /// { + /// } + /// + /// public RequireBotPermissionAttribute(ChannelPermission permission) { ChannelPermission = permission; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 1cd32e72e..beadbbc89 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -11,11 +11,27 @@ namespace Discord.Commands Group = 0x04 } + /// + /// Require that the command be invoked in a specified context. + /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireContextAttribute : PreconditionAttribute { public ContextType Contexts { get; } + /// + /// Require that the command be invoked in a specified context. + /// + /// The type of context the command can be invoked in. Multiple contexts can be speicifed by ORing the contexts together. + /// + /// + /// [Command("private_only")] + /// [RequireContext(ContextType.DM | ContextType.Group)] + /// public async Task PrivateOnly() + /// { + /// } + /// + /// public RequireContextAttribute(ContextType contexts) { Contexts = contexts; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index e03e3971b..beb8dfdd0 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -6,6 +6,10 @@ using Discord; namespace Discord.Commands { + /// + /// Require that the command is invoked by the owner of the bot. + /// + /// This precondition will only work if the bot is a bot account. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireOwnerAttribute : PreconditionAttribute { diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 2e19b61cf..17cf234aa 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -3,17 +3,39 @@ using System.Threading.Tasks; namespace Discord.Commands { + /// + /// This attribute requires that the user invoking the command has a specified permission. + /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RequireUserPermissionAttribute : PreconditionAttribute { public GuildPermission? GuildPermission { get; } public ChannelPermission? ChannelPermission { get; } + /// + /// Require that the user invoking the command has a specified GuildPermission + /// + /// This precondition will always fail if the command is being invoked in a private channel. + /// The GuildPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together. public RequireUserPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } + /// + /// Require that the user invoking the command has a specified ChannelPermission. + /// + /// The ChannelPermission that the user must have. Multiple permissions can be specified by ORing or ANDing the permissions together. + /// + /// + /// [Command("permission")] + /// [RequireUserPermission(ChannelPermission.ReadMessageHistory & ChannelPermission.ReadMessages)] + /// public async Task HasPermission() + /// { + /// await ReplyAsync("You can read messages and the message history!"); + /// } + /// + /// public RequireUserPermissionAttribute(ChannelPermission permission) { ChannelPermission = permission; From f9c5e229d0fb5b66939d7703a3c109c70c7b3605 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 18:49:35 -0500 Subject: [PATCH 77/88] Add CommandServiceConfig, DefaultRunMode This adds an (optional) CommandServiceConfig, as well as a DefaultRunMode for commands. This resolves #368 (for commands where a RunMode is not explicitly specified, a custom default value should be used) --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- src/Discord.Net.Commands/Builders/CommandBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 6 +++++- src/Discord.Net.Commands/CommandServiceConfig.cs | 8 ++++++++ src/Discord.Net.Commands/Info/CommandInfo.cs | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/Discord.Net.Commands/CommandServiceConfig.cs diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index baac75ff9..86daf0103 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -6,7 +6,7 @@ namespace Discord.Commands public class CommandAttribute : Attribute { public string Text { get; } - public RunMode RunMode { get; set; } = RunMode.Sync; + public RunMode RunMode { get; set; } public CommandAttribute() { diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 9b983fd1f..25c0223e6 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -17,7 +17,7 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public RunMode RunMode { get; set; } + public RunMode? RunMode { get; set; } public int Priority { get; set; } public IReadOnlyList Preconditions => _preconditions; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 3c3760908..376eca84e 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -19,10 +19,13 @@ namespace Discord.Commands private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; + internal readonly RunMode _defaultRunMode; + public IEnumerable Modules => _typedModuleDefs.Select(x => x.Value); public IEnumerable Commands => _typedModuleDefs.SelectMany(x => x.Value.Commands); - public CommandService() + public CommandService() : this(new CommandServiceConfig()) { } + public CommandService(CommandServiceConfig config) { _moduleLock = new SemaphoreSlim(1, 1); _typedModuleDefs = new ConcurrentDictionary(); @@ -64,6 +67,7 @@ namespace Discord.Commands [typeof(IGroupUser)] = new UserTypeReader(), [typeof(IGuildUser)] = new UserTypeReader(), }; + _defaultRunMode = config.DefaultRunMode; } //Modules diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs new file mode 100644 index 000000000..97c98a54c --- /dev/null +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -0,0 +1,8 @@ +namespace Discord.Commands +{ + public class CommandServiceConfig + { + /// The default RunMode commands should have, if one is not specified on the Command attribute or builder. + public RunMode DefaultRunMode { get; set; } = RunMode.Mixed; + } +} diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 4d546b6fa..a9a3c2415 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -37,7 +37,7 @@ namespace Discord.Commands Summary = builder.Summary; Remarks = builder.Remarks; - RunMode = builder.RunMode; + RunMode = builder.RunMode ?? service._defaultRunMode; Priority = builder.Priority; if (module.Aliases.Count != 0) From 58291f819968aee84e3c13114620f55f520577f4 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 21:45:56 -0500 Subject: [PATCH 78/88] Close the WebSocket when disconnecting This resolves #224 --- src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs index 707f7663b..ffa96dba7 100644 --- a/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs +++ b/src/Discord.Net.Core/Net/WebSockets/DefaultWebSocketClient.cs @@ -101,6 +101,8 @@ namespace Discord.Net.WebSockets if (_client != null && _client.State == WebSocketState.Open) { + var token = new CancellationToken(); + await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token); _client.Dispose(); _client = null; } From 6519b300d9394b662d3306146e048fa24702d73f Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 22:12:28 -0500 Subject: [PATCH 79/88] ModifyAsync will accept an EmbedBuilder **This is not a breaking change**. This change begins to implement #379, where IUserMessage.ModifyAsync will now accept a `Discord.ModifyMessageParams` func over a `Discord.API.Rest.ModifyMessageParams` func. --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 2 +- src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs | 2 ++ .../Entities/Messages/IUserMessage.cs | 3 +-- .../Entities/Messages/ModifyMessageParams.cs | 12 ++++++++++++ .../Entities/Messages/MessageHelper.cs | 7 ++++++- 5 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index d050f12a4..e280c730f 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -498,7 +498,7 @@ namespace Discord.API break; } } - public async Task ModifyMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, RequestOptions options = null) + public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); diff --git a/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs index 4901ddc9d..c87d82c51 100644 --- a/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Core/API/Rest/ModifyMessageParams.cs @@ -8,5 +8,7 @@ namespace Discord.API.Rest { [JsonProperty("content")] public Optional Content { get; set; } + [JsonProperty("embed")] + public Optional Embed { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index a9dc4735c..c33b3e359 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -1,5 +1,4 @@ -using Discord.API.Rest; -using System; +using System; using System.Threading.Tasks; namespace Discord diff --git a/src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs b/src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs new file mode 100644 index 000000000..cc05e620a --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ModifyMessageParams.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Discord +{ + public class ModifyMessageParams + { + public Optional Content { get; set; } + public Optional Embed { get; set; } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 358f6f5a9..91ca2ce1b 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -15,7 +15,12 @@ namespace Discord.Rest { var args = new ModifyMessageParams(); func(args); - return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, args, options).ConfigureAwait(false); + var apiArgs = new API.Rest.ModifyMessageParams + { + Content = args.Content, + Embed = args.Embed.IsSpecified ? args.Embed.Value.Build() : Optional.Create() + }; + return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } public static async Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) From 08b836797bd902b1e3cd10472856f2db145643b8 Mon Sep 17 00:00:00 2001 From: Sindre Langhus Date: Sat, 26 Nov 2016 04:43:29 +0100 Subject: [PATCH 80/88] Current timestamp namechange --- src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index aac361888..2fba6d47e 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -55,7 +55,7 @@ namespace Discord ImageUrl = ImageUrl; return this; } - public EmbedBuilder WithTimestamp() + public EmbedBuilder WithCurrentTimestamp() { Timestamp = DateTimeOffset.UtcNow; return this; From 1ed4f703bf61b9ab8af7c3d64d266154265fc236 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 14:59:20 -0500 Subject: [PATCH 81/88] Cache the current application on DiscordSocketClient --- .../Attributes/Preconditions/RequireOwnerAttribute.cs | 5 +---- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index beb8dfdd0..8992cd115 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -13,12 +13,9 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireOwnerAttribute : PreconditionAttribute { - private IApplication application; - public override async Task CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) { - if (application == null) - application = await context.Client.GetApplicationInfoAsync(); + var application = await context.Client.GetApplicationInfoAsync(); if (context.User.Id == application.Owner.Id) return PreconditionResult.FromSuccess(); return PreconditionResult.FromError("Command can only be run by the owner of the bot"); } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 3eb4158d1..8c6a37f3c 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -38,6 +38,7 @@ namespace Discord.WebSocket private int _nextAudioId; private bool _canReconnect; private DateTimeOffset? _statusSince; + private RestApplication _application; /// Gets the shard of of this client. public int ShardId { get; } @@ -333,8 +334,10 @@ namespace Discord.WebSocket } /// - public Task GetApplicationInfoAsync() - => ClientHelper.GetApplicationInfoAsync(this); + public async Task GetApplicationInfoAsync() + { + return _application ?? (_application = await ClientHelper.GetApplicationInfoAsync(this)); + } /// public SocketGuild GetGuild(ulong id) From 1be6f77efb623878d085b1012adae0aeb06f06eb Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 21:22:35 -0500 Subject: [PATCH 82/88] Don't check message content if an embed is present when modifying --- src/Discord.Net.Core/API/DiscordRestApiClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/API/DiscordRestApiClient.cs b/src/Discord.Net.Core/API/DiscordRestApiClient.cs index e280c730f..6ec7c5fb2 100644 --- a/src/Discord.Net.Core/API/DiscordRestApiClient.cs +++ b/src/Discord.Net.Core/API/DiscordRestApiClient.cs @@ -505,7 +505,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); if (args.Content.IsSpecified) { - Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + if (!args.Embed.IsSpecified) + Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); } From 210c360fca85268c4697c4c4669af904f0e298af Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 21:32:21 -0500 Subject: [PATCH 83/88] Throw an exception when creating a Color with an invalid float value This prevents a 400 when sending a malformed color to Discord. --- src/Discord.Net.Core/Entities/Roles/Color.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 563917959..d518d6edd 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -32,6 +32,12 @@ namespace Discord } public Color(float r, float g, float b) { + if (r <= 0.0f && r >= 1.0f) + throw new ArgumentOutOfRangeException(nameof(r), "A float value must be within [0,1]"); + if (g <= 0.0f || g >= 1.0f) + throw new ArgumentOutOfRangeException(nameof(g), "A float value must be within [0,1]"); + if (b <= 0.0f || b >= 1.0f) + throw new ArgumentOutOfRangeException(nameof(b), "A float value must be within [0,1]"); RawValue = ((uint)(r * 255.0f) << 16) | ((uint)(g * 255.0f) << 8) | From fb99b019a0c031fcb930394e32723df8d6793108 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 22:04:02 -0500 Subject: [PATCH 84/88] Fix issues with DefaultRunMode For a command to use the DefaultRunMode, it must now have it's RunMode set to RunMode.Default (this is the default value on CommandAttribute now). --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- src/Discord.Net.Commands/Builders/CommandBuilder.cs | 2 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 2 +- src/Discord.Net.Commands/RunMode.cs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index 86daf0103..5ae6092eb 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -6,7 +6,7 @@ namespace Discord.Commands public class CommandAttribute : Attribute { public string Text { get; } - public RunMode RunMode { get; set; } + public RunMode RunMode { get; set; } = RunMode.Default; public CommandAttribute() { diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 25c0223e6..9b983fd1f 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -17,7 +17,7 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } - public RunMode? RunMode { get; set; } + public RunMode RunMode { get; set; } public int Priority { get; set; } public IReadOnlyList Preconditions => _preconditions; diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index a9a3c2415..24f262482 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -37,7 +37,7 @@ namespace Discord.Commands Summary = builder.Summary; Remarks = builder.Remarks; - RunMode = builder.RunMode ?? service._defaultRunMode; + RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); Priority = builder.Priority; if (module.Aliases.Count != 0) diff --git a/src/Discord.Net.Commands/RunMode.cs b/src/Discord.Net.Commands/RunMode.cs index 0799f825c..2bb5dbbf6 100644 --- a/src/Discord.Net.Commands/RunMode.cs +++ b/src/Discord.Net.Commands/RunMode.cs @@ -2,6 +2,7 @@ { public enum RunMode { + Default, Sync, Mixed, Async From 9d4339e6952b10c010ec9f2e2197cc8a78aaf803 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 22:13:43 -0500 Subject: [PATCH 85/88] Fix a few typos in Color constraint logic --- src/Discord.Net.Core/Entities/Roles/Color.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index d518d6edd..ead46fd8a 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -32,11 +32,11 @@ namespace Discord } public Color(float r, float g, float b) { - if (r <= 0.0f && r >= 1.0f) + if (r < 0.0f || r > 1.0f) throw new ArgumentOutOfRangeException(nameof(r), "A float value must be within [0,1]"); - if (g <= 0.0f || g >= 1.0f) + if (g < 0.0f || g > 1.0f) throw new ArgumentOutOfRangeException(nameof(g), "A float value must be within [0,1]"); - if (b <= 0.0f || b >= 1.0f) + if (b < 0.0f || b > 1.0f) throw new ArgumentOutOfRangeException(nameof(b), "A float value must be within [0,1]"); RawValue = ((uint)(r * 255.0f) << 16) | From d74257cb6904611a442c56da795eb4c57342cf03 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 26 Nov 2016 23:51:23 -0400 Subject: [PATCH 86/88] Drop applicationinfo cache on logout, fixed RPC appinfo exception. --- src/Discord.Net.Rest/DiscordRestClient.cs | 13 +++++++++++-- src/Discord.Net.Rpc/DiscordRpcClient.cs | 9 ++++++--- src/Discord.Net.WebSocket/DiscordSocketClient.cs | 5 +++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index f36c0fb06..8c1bd45da 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -8,6 +8,8 @@ namespace Discord.Rest { public class DiscordRestClient : BaseDiscordClient, IDiscordClient { + private RestApplication _applicationInfo; + public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; public DiscordRestClient() : this(new DiscordRestConfig()) { } @@ -21,10 +23,17 @@ namespace Discord.Rest base.CurrentUser = RestSelfUser.Create(this, ApiClient.CurrentUser); return Task.CompletedTask; } + protected override Task OnLogoutAsync() + { + _applicationInfo = null; + return Task.CompletedTask; + } /// - public Task GetApplicationInfoAsync() - => ClientHelper.GetApplicationInfoAsync(this); + public async Task GetApplicationInfoAsync() + { + return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); + } /// public Task GetChannelAsync(ulong id) diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 52fe6172f..3ca288b12 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Discord.Rpc { - public partial class DiscordRpcClient : BaseDiscordClient + public partial class DiscordRpcClient : BaseDiscordClient, IDiscordClient { private readonly Logger _rpcLogger; private readonly JsonSerializer _serializer; @@ -33,7 +33,7 @@ namespace Discord.Rpc public new API.DiscordRpcApiClient ApiClient => base.ApiClient as API.DiscordRpcApiClient; public new RestSelfUser CurrentUser { get { return base.CurrentUser as RestSelfUser; } private set { base.CurrentUser = value; } } - public RestApplication CurrentApplication { get; private set; } + public RestApplication ApplicationInfo { get; private set; } /// Creates a new RPC discord client. public DiscordRpcClient(string clientId, string origin) @@ -393,7 +393,7 @@ namespace Discord.Rpc { var response = await ApiClient.SendAuthenticateAsync(options).ConfigureAwait(false); CurrentUser = RestSelfUser.Create(this, response.User); - CurrentApplication = RestApplication.Create(this, response.Application); + ApplicationInfo = RestApplication.Create(this, response.Application); Scopes = response.Scopes; TokenExpiresAt = response.Expires; @@ -547,5 +547,8 @@ namespace Discord.Rpc return; } } + + //IDiscordClient + Task IDiscordClient.GetApplicationInfoAsync() => Task.FromResult(ApplicationInfo); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 8c6a37f3c..3e208ce58 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -38,7 +38,7 @@ namespace Discord.WebSocket private int _nextAudioId; private bool _canReconnect; private DateTimeOffset? _statusSince; - private RestApplication _application; + private RestApplication _applicationInfo; /// Gets the shard of of this client. public int ShardId { get; } @@ -124,6 +124,7 @@ namespace Discord.WebSocket if (ConnectionState != ConnectionState.Disconnected) await DisconnectInternalAsync(null, false).ConfigureAwait(false); + _applicationInfo = null; _voiceRegions = ImmutableDictionary.Create(); } @@ -336,7 +337,7 @@ namespace Discord.WebSocket /// public async Task GetApplicationInfoAsync() { - return _application ?? (_application = await ClientHelper.GetApplicationInfoAsync(this)); + return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this)); } /// From ecc3d9c729add86cb6bc5acbb61fd01cbd44139f Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 25 Nov 2016 23:56:20 -0500 Subject: [PATCH 87/88] Add configuration option for case insensitive commands Currently, commands are case-sensitive. This PR allows for commands to be case insensitive (which is now the default option). --- src/Discord.Net.Commands/CommandService.cs | 4 +++- src/Discord.Net.Commands/CommandServiceConfig.cs | 2 ++ src/Discord.Net.Commands/Info/CommandInfo.cs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 9420476d5..26d811440 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -19,6 +19,7 @@ namespace Discord.Commands private readonly ConcurrentBag _moduleDefs; private readonly CommandMap _map; + internal readonly bool _caseSensitive; internal readonly RunMode _defaultRunMode; public IEnumerable Modules => _moduleDefs.Select(x => x); @@ -67,6 +68,7 @@ namespace Discord.Commands [typeof(IGroupUser)] = new UserTypeReader(), [typeof(IGuildUser)] = new UserTypeReader(), }; + _caseSensitive = config.CaseSensitiveCommands; _defaultRunMode = config.DefaultRunMode; } @@ -212,7 +214,7 @@ namespace Discord.Commands public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); public SearchResult Search(CommandContext context, string input) { - string lowerInput = input.ToLowerInvariant(); + input = _caseSensitive ? input : input.ToLowerInvariant(); var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); if (matches.Length > 0) diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 97c98a54c..4ac79fe8f 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -4,5 +4,7 @@ { /// The default RunMode commands should have, if one is not specified on the Command attribute or builder. public RunMode DefaultRunMode { get; set; } = RunMode.Mixed; + /// Should commands be case-sensitive? + public bool CaseSensitiveCommands { get; set; } = false; } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 2ca8b1651..b4422e911 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -7,9 +7,11 @@ using System.Threading.Tasks; using System.Reflection; using Discord.Commands.Builders; +using System.Diagnostics; namespace Discord.Commands { + [DebuggerDisplay("{Name,nq}")] public class CommandInfo { private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); From d72122eef9d32d8a094effe2cd305ec3196a9e78 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 26 Nov 2016 23:15:36 -0500 Subject: [PATCH 88/88] Resolve conflicts. --- src/Discord.Net.Commands/Info/CommandInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index b4422e911..571c47e13 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -44,13 +44,13 @@ namespace Discord.Commands // both command and module provide aliases if (module.Aliases.Count > 0 && builder.Aliases.Count > 0) - Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => second != null ? first + " " + second : first).ToImmutableArray(); + Aliases = module.Aliases.Permutate(builder.Aliases, (first, second) => second != null ? first + " " + second : first).Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); // only module provides aliases else if (module.Aliases.Count > 0) - Aliases = module.Aliases.ToImmutableArray(); + Aliases = module.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); // only command provides aliases else if (builder.Aliases.Count > 0) - Aliases = builder.Aliases.ToImmutableArray(); + Aliases = builder.Aliases.Select(x => service._caseSensitive ? x : x.ToLowerInvariant()).ToImmutableArray(); // neither provide aliases else throw new InvalidOperationException("Cannot build a command without any aliases");