From db90eab9534967a9b4a43436991711a372aec320 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Tue, 21 Aug 2018 06:27:50 +0800 Subject: [PATCH 01/59] Update README.md (#1117) * Initial commit * Add alternative suggestion * Fix typo * Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index bd0ef20c7..7dc8cd788 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,9 @@ Our stable builds available from NuGet through the Discord.Net metapackage: The individual components may also be installed from NuGet: - [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) - [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) -- [Discord.Net.Rpc](https://www.nuget.org/packages/Discord.Net.Rpc/) - [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) - [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) -The following provider is available for platforms not supporting .NET Standard 1.3: -- [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) - ### Unstable (MyGet) Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). @@ -41,5 +37,4 @@ The .NET Core workload must be selected during Visual Studio installation. ## Known Issues ### WebSockets (Win7 and earlier) -.NET Core 1.1 does not support WebSockets on Win7 and earlier. It's recommended to use the Discord.Net.Providers.WS4Net package until this is resolved. -Track the issue [here](https://github.com/dotnet/corefx/issues/9503). +.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. From 4259b8cb0cfb63e66cde9d0f2c3e7de5a59971ab Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 00:11:55 -0400 Subject: [PATCH 02/59] Update CommandAttribute.cs Nullable is not valid for Attributes, this is my suggested fix. --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a0fcf3e4a..a9df3b409 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -17,5 +17,10 @@ namespace Discord.Commands { Text = text; } + public CommandAttribute(string text, bool ignoreExtraArgs) + { + Text = text; + IgnoreExtraArgs = ignoreExtraArgs; + } } } From c56fff9fe55648cdb09325bf95bd244a125f0adc Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 00:44:35 -0400 Subject: [PATCH 03/59] Update CommandAttribute.cs --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a9df3b409..ac3ba599e 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands { public string Text { get; } public RunMode RunMode { get; set; } = RunMode.Default; - public bool? IgnoreExtraArgs { get; set; } + public bool? IgnoreExtraArgs { get; private set; } public CommandAttribute() { From 143fb2808bcf034a1cbb4231fc19424972a22ca4 Mon Sep 17 00:00:00 2001 From: JustNrik <35231903+JustNrik@users.noreply.github.com> Date: Mon, 27 Aug 2018 17:39:00 -0400 Subject: [PATCH 04/59] Update CommandAttribute.cs --- src/Discord.Net.Commands/Attributes/CommandAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index ac3ba599e..bfc04641a 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -7,7 +7,7 @@ namespace Discord.Commands { public string Text { get; } public RunMode RunMode { get; set; } = RunMode.Default; - public bool? IgnoreExtraArgs { get; private set; } + public bool? IgnoreExtraArgs { get; } public CommandAttribute() { From efdb4f926698bbf6ef2d93779291f7f39d675b3f Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 29 Aug 2018 17:11:26 +0100 Subject: [PATCH 05/59] Pass our json serializer to ToObject calls (#1133) Should fix issues in audit logs when deserializing overwrites and similar types which we use a custom contract resolver for. --- .../API/Common/AuditLogOptions.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 22 +++++----- .../DataTypes/ChannelCreateAuditLogData.cs | 8 ++-- .../DataTypes/ChannelDeleteAuditLogData.cs | 6 +-- .../DataTypes/ChannelUpdateAuditLogData.cs | 16 ++++---- .../DataTypes/EmoteCreateAuditLogData.cs | 2 +- .../DataTypes/EmoteDeleteAuditLogData.cs | 2 +- .../DataTypes/EmoteUpdateAuditLogData.cs | 4 +- .../DataTypes/GuildUpdateAuditLogData.cs | 40 +++++++++---------- .../DataTypes/InviteCreateAuditLogData.cs | 14 +++---- .../DataTypes/InviteDeleteAuditLogData.cs | 14 +++---- .../DataTypes/InviteUpdateAuditLogData.cs | 20 +++++----- .../DataTypes/MemberRoleAuditLogData.cs | 2 +- .../DataTypes/MemberUpdateAuditLogData.cs | 16 ++++---- .../DataTypes/OverwriteCreateAuditLogData.cs | 8 ++-- .../DataTypes/OverwriteDeleteAuditLogData.cs | 12 +++--- .../DataTypes/OverwriteUpdateAuditLogData.cs | 12 +++--- .../DataTypes/RoleCreateAuditLogData.cs | 10 ++--- .../DataTypes/RoleDeleteAuditLogData.cs | 10 ++--- .../DataTypes/RoleUpdateAuditLogData.cs | 20 +++++----- .../DataTypes/WebhookCreateAuditLogData.cs | 6 +-- .../DataTypes/WebhookDeleteAuditLogData.cs | 8 ++-- .../DataTypes/WebhookUpdateAuditLogData.cs | 14 +++---- 23 files changed, 132 insertions(+), 136 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs index 65b401cce..24141d90c 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -20,7 +20,7 @@ namespace Discord.API [JsonProperty("role_name")] public string OverwriteRoleName { get; set; } [JsonProperty("type")] - public string OverwriteType { get; set; } + public PermissionTarget OverwriteType { get; set; } [JsonProperty("id")] public ulong? OverwriteTargetId { get; set; } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 2236dbbf8..a9fc1ed74 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -43,9 +43,11 @@ namespace Discord.API public TokenType AuthTokenType { get; private set; } internal string AuthToken { get; private set; } internal IRestClient RestClient { get; private set; } - internal ulong? CurrentUserId { get; set;} + internal ulong? CurrentUserId { get; set; } - public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, + internal JsonSerializer Serializer => _serializer; + + public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) { _restClientProvider = restClientProvider; @@ -235,7 +237,7 @@ namespace Discord.API internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, 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, + public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) { options = options ?? new RequestOptions(); @@ -414,7 +416,7 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); } - + //Channel Messages public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -490,7 +492,7 @@ namespace Discord.API 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); - + return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) @@ -737,7 +739,7 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - + return await SendJsonAsync("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteGuildAsync(ulong guildId, RequestOptions options = null) @@ -964,7 +966,7 @@ namespace Discord.API { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - + return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); } @@ -1163,7 +1165,7 @@ namespace Discord.API int limit = args.Limit.GetValueOrDefault(int.MaxValue); ulong afterGuildId = args.AfterGuildId.GetValueOrDefault(0); - + return await SendAsync>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); } public async Task GetMyApplicationAsync(RequestOptions options = null) @@ -1263,7 +1265,7 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - + if (AuthTokenType == TokenType.Webhook) return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}", args, new BucketIds(), options: options).ConfigureAwait(false); else @@ -1393,7 +1395,7 @@ namespace Discord.API int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); string fieldName = GetFieldName(methodArgs[argId + 1]); int? mappedId; - + mappedId = BucketIds.GetIndex(fieldName); if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs index ef4787295..51e72c414 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -26,18 +26,16 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var type = typeModel.NewValue.ToObject(); - var name = nameModel.NewValue.ToObject(); + var type = typeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.NewValue.ToObject(discord.ApiClient.Serializer); foreach (var overwrite in overwritesModel.NewValue) { var deny = overwrite.Value("deny"); - var _type = overwrite.Value("type"); + var permType = overwrite.Value("type"); var id = overwrite.Value("id"); var allow = overwrite.Value("allow"); - PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; - overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs index 4816ce770..7af5ca10c 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -27,11 +27,11 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var overwrites = overwritesModel.OldValue.ToObject() + var overwrites = overwritesModel.OldValue.ToObject(discord.ApiClient.Serializer) .Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) .ToList(); - var type = typeModel.OldValue.ToObject(); - var name = nameModel.OldValue.ToObject(); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.OldValue.ToObject(discord.ApiClient.Serializer); var id = entry.TargetId.Value; return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index 491cb5717..36fe82084 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -23,14 +23,14 @@ namespace Discord.Rest var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - string oldTopic = topicModel?.OldValue?.ToObject(), - newTopic = topicModel?.NewValue?.ToObject(); - int? oldBitrate = bitrateModel?.OldValue?.ToObject(), - newBitrate = bitrateModel?.NewValue?.ToObject(); - int? oldLimit = userLimitModel?.OldValue?.ToObject(), - newLimit = userLimitModel?.NewValue?.ToObject(); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldTopic = topicModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newTopic = topicModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldBitrate = bitrateModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newBitrate = bitrateModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldLimit = userLimitModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newLimit = userLimitModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit); var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs index 5d1ef8463..dac2d90ef 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -21,7 +21,7 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var emoteName = change.NewValue?.ToObject(); + var emoteName = change.NewValue?.ToObject(discord.ApiClient.Serializer); return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs index d0a11191f..73cb31af9 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -17,7 +17,7 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var emoteName = change.OldValue?.ToObject(); + var emoteName = change.OldValue?.ToObject(discord.ApiClient.Serializer); return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs index 60020bcaa..84898013d 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -18,8 +18,8 @@ namespace Discord.Rest { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var newName = change.NewValue?.ToObject(); - var oldName = change.OldValue?.ToObject(); + var newName = change.NewValue?.ToObject(discord.ApiClient.Serializer); + var oldName = change.OldValue?.ToObject(discord.ApiClient.Serializer); return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs index 08550ed7a..09c1eda18 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -28,26 +28,26 @@ namespace Discord.Rest var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); - int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(), - newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(); - DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(), - newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(); - ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(), - newAfkChannelId = afkChannelModel?.NewValue?.ToObject(); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - string oldRegionId = regionIdModel?.OldValue?.ToObject(), - newRegionId = regionIdModel?.NewValue?.ToObject(); - string oldIconHash = iconHashModel?.OldValue?.ToObject(), - newIconHash = iconHashModel?.NewValue?.ToObject(); - VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(), - newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(); - ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(), - newOwnerId = ownerIdModel?.NewValue?.ToObject(); - MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(), - newMfaLevel = mfaLevelModel?.NewValue?.ToObject(); - int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(), - newContentFilter = contentFilterModel?.NewValue?.ToObject(); + int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAfkChannelId = afkChannelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldRegionId = regionIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newRegionId = regionIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldIconHash = iconHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newIconHash = iconHashModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newVerificationLevel = verificationLevelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newOwnerId = ownerIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMfaLevel = mfaLevelModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldContentFilter = contentFilterModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newContentFilter = contentFilterModel?.NewValue?.ToObject(discord.ApiClient.Serializer); IUser oldOwner = null; if (oldOwnerId != null) diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs index 292715420..1d7f48e93 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -30,13 +30,13 @@ namespace Discord.Rest var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - var maxAge = maxAgeModel.NewValue.ToObject(); - var code = codeModel.NewValue.ToObject(); - var temporary = temporaryModel.NewValue.ToObject(); - var inviterId = inviterIdModel.NewValue.ToObject(); - var channelId = channelIdModel.NewValue.ToObject(); - var uses = usesModel.NewValue.ToObject(); - var maxUses = maxUsesModel.NewValue.ToObject(); + var maxAge = maxAgeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var code = codeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var temporary = temporaryModel.NewValue.ToObject(discord.ApiClient.Serializer); + var inviterId = inviterIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var channelId = channelIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var uses = usesModel.NewValue.ToObject(discord.ApiClient.Serializer); + var maxUses = maxUsesModel.NewValue.ToObject(discord.ApiClient.Serializer); var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); var inviter = RestUser.Create(discord, inviterInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs index 1dc6d518b..091285532 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -30,13 +30,13 @@ namespace Discord.Rest var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - var maxAge = maxAgeModel.OldValue.ToObject(); - var code = codeModel.OldValue.ToObject(); - var temporary = temporaryModel.OldValue.ToObject(); - var inviterId = inviterIdModel.OldValue.ToObject(); - var channelId = channelIdModel.OldValue.ToObject(); - var uses = usesModel.OldValue.ToObject(); - var maxUses = maxUsesModel.OldValue.ToObject(); + var maxAge = maxAgeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var code = codeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var temporary = temporaryModel.OldValue.ToObject(discord.ApiClient.Serializer); + var inviterId = inviterIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var channelId = channelIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var uses = usesModel.OldValue.ToObject(discord.ApiClient.Serializer); + var maxUses = maxUsesModel.OldValue.ToObject(discord.ApiClient.Serializer); var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); var inviter = RestUser.Create(discord, inviterInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs index b932cfbfc..35088be98 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -23,16 +23,16 @@ namespace Discord.Rest var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); - int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(), - newMaxAge = maxAgeModel?.NewValue?.ToObject(); - string oldCode = codeModel?.OldValue?.ToObject(), - newCode = codeModel?.NewValue?.ToObject(); - bool? oldTemporary = temporaryModel?.OldValue?.ToObject(), - newTemporary = temporaryModel?.NewValue?.ToObject(); - ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(), - newChannelId = channelIdModel?.NewValue?.ToObject(); - int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(), - newMaxUses = maxUsesModel?.NewValue?.ToObject(); + int? oldMaxAge = maxAgeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMaxAge = maxAgeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldCode = codeModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newCode = codeModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldTemporary = temporaryModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newTemporary = temporaryModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldChannelId = channelIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newChannelId = channelIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + int? oldMaxUses = maxUsesModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMaxUses = maxUsesModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses); var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 3bcbce440..48adb1833 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -19,7 +19,7 @@ namespace Discord.Rest { var changes = entry.Changes; - var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(discord.ApiClient.Serializer), (model, role) => new { model.ChangedProperty, Role = role }) .Select(x => new MemberRoleEditInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) .ToList(); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 40a3ba681..96d34610e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -24,14 +24,14 @@ namespace Discord.Rest var muteModel = changes.FirstOrDefault(x => x.ChangedProperty == "mute"); var avatarModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - string oldNick = nickModel?.OldValue?.ToObject(), - newNick = nickModel?.NewValue?.ToObject(); - bool? oldDeaf = deafModel?.OldValue?.ToObject(), - newDeaf = deafModel?.NewValue?.ToObject(); - bool? oldMute = muteModel?.OldValue?.ToObject(), - newMute = muteModel?.NewValue?.ToObject(); - string oldAvatar = avatarModel?.OldValue?.ToObject(), - newAvatar = avatarModel?.NewValue?.ToObject(); + string oldNick = nickModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newNick = nickModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldDeaf = deafModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newDeaf = deafModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldMute = muteModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMute = muteModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldAvatar = avatarModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newAvatar = avatarModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); var user = RestUser.Create(discord, targetInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs index d58488136..b13f4b8fd 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -19,17 +19,15 @@ namespace Discord.Rest var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var deny = denyModel.NewValue.ToObject(); - var allow = allowModel.NewValue.ToObject(); + var deny = denyModel.NewValue.ToObject(discord.ApiClient.Serializer); + var allow = allowModel.NewValue.ToObject(discord.ApiClient.Serializer); var permissions = new OverwritePermissions(allow, deny); var id = entry.Options.OverwriteTargetId.Value; var type = entry.Options.OverwriteType; - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; - - return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); + return new OverwriteCreateAuditLogData(new Overwrite(id, type, permissions)); } public Overwrite Overwrite { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 445c2e302..5d5177d9a 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -27,14 +27,12 @@ namespace Discord.Rest var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var deny = denyModel.OldValue.ToObject(); - var type = typeModel.OldValue.ToObject(); - var id = idModel.OldValue.ToObject(); - var allow = allowModel.OldValue.ToObject(); + var deny = denyModel.OldValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var id = idModel.OldValue.ToObject(discord.ApiClient.Serializer); + var allow = allowModel.OldValue.ToObject(discord.ApiClient.Serializer); - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; - - return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); + return new OverwriteDeleteAuditLogData(new Overwrite(id, type, new OverwritePermissions(allow, deny))); } public Overwrite Overwrite { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs index d000146c3..d05e1feff 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -22,17 +22,17 @@ namespace Discord.Rest var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); - var beforeAllow = allowModel?.OldValue?.ToObject(); - var afterAllow = allowModel?.NewValue?.ToObject(); - var beforeDeny = denyModel?.OldValue?.ToObject(); - var afterDeny = denyModel?.OldValue?.ToObject(); + var beforeAllow = allowModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var afterAllow = allowModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var beforeDeny = denyModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var afterDeny = denyModel?.OldValue?.ToObject(discord.ApiClient.Serializer); var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); - PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; + var type = entry.Options.OverwriteType; - return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, type); } public OverwritePermissions OldPermissions { get; } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs index dcc1c6ab6..69e72fdb0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -23,11 +23,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.NewValue?.ToObject(); - bool? mentionable = mentionableModel?.NewValue?.ToObject(); - bool? hoist = hoistModel?.NewValue?.ToObject(); - string name = nameModel?.NewValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(); + uint? colorRaw = colorModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? mentionable = mentionableModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? hoist = hoistModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string name = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(discord.ApiClient.Serializer); Color? color = null; GuildPermissions? permissions = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs index 263909daf..f812567cb 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -23,11 +23,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.OldValue?.ToObject(); - bool? mentionable = mentionableModel?.OldValue?.ToObject(); - bool? hoist = hoistModel?.OldValue?.ToObject(); - string name = nameModel?.OldValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(); + uint? colorRaw = colorModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + bool? mentionable = mentionableModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + bool? hoist = hoistModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + string name = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer); Color? color = null; GuildPermissions? permissions = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs index b645ef7ae..5cea865f1 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -24,16 +24,16 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? oldColorRaw = colorModel?.OldValue?.ToObject(), - newColorRaw = colorModel?.NewValue?.ToObject(); - bool? oldMentionable = mentionableModel?.OldValue?.ToObject(), - newMentionable = mentionableModel?.NewValue?.ToObject(); - bool? oldHoist = hoistModel?.OldValue?.ToObject(), - newHoist = hoistModel?.NewValue?.ToObject(); - string oldName = nameModel?.OldValue?.ToObject(), - newName = nameModel?.NewValue?.ToObject(); - ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(), - newPermissionsRaw = permissionsModel?.OldValue?.ToObject(); + uint? oldColorRaw = colorModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newColorRaw = colorModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldMentionable = mentionableModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newMentionable = mentionableModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + bool? oldHoist = hoistModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newHoist = hoistModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + string oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer), + newPermissionsRaw = permissionsModel?.OldValue?.ToObject(discord.ApiClient.Serializer); Color? oldColor = null, newColor = null; diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs index 1ae45fb8c..06932bfc4 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -23,9 +23,9 @@ namespace Discord.Rest var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); - var channelId = channelIdModel.NewValue.ToObject(); - var type = typeModel.NewValue.ToObject(); - var name = nameModel.NewValue.ToObject(); + var channelId = channelIdModel.NewValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.NewValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.NewValue.ToObject(discord.ApiClient.Serializer); var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs index 4133d5dff..8fc4da578 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -29,10 +29,10 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - var channelId = channelIdModel.OldValue.ToObject(); - var type = typeModel.OldValue.ToObject(); - var name = nameModel.OldValue.ToObject(); - var avatarHash = avatarHashModel?.OldValue?.ToObject(); + var channelId = channelIdModel.OldValue.ToObject(discord.ApiClient.Serializer); + var type = typeModel.OldValue.ToObject(discord.ApiClient.Serializer); + var name = nameModel.OldValue.ToObject(discord.ApiClient.Serializer); + var avatarHash = avatarHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer); return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs index 54da42a8b..ad7db53e2 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -26,18 +26,18 @@ namespace Discord.Rest var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); - var oldName = nameModel?.OldValue?.ToObject(); - var oldChannelId = channelIdModel?.OldValue?.ToObject(); - var oldAvatar = avatarHashModel?.OldValue?.ToObject(); + var oldName = nameModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var oldChannelId = channelIdModel?.OldValue?.ToObject(discord.ApiClient.Serializer); + var oldAvatar = avatarHashModel?.OldValue?.ToObject(discord.ApiClient.Serializer); var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); - var newName = nameModel?.NewValue?.ToObject(); - var newChannelId = channelIdModel?.NewValue?.ToObject(); - var newAvatar = avatarHashModel?.NewValue?.ToObject(); + var newName = nameModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var newChannelId = channelIdModel?.NewValue?.ToObject(discord.ApiClient.Serializer); + var newAvatar = avatarHashModel?.NewValue?.ToObject(discord.ApiClient.Serializer); var after = new WebhookInfo(newName, newChannelId, newAvatar); var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); - var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); + var webhook = webhookInfo != null ? RestWebhook.Create(discord, (IGuild)null, webhookInfo) : null; return new WebhookUpdateAuditLogData(webhook, before, after); } From 2de6cef18c495aaa0cb70b1496ea528b47337fd2 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 30 Aug 2018 14:27:37 -0700 Subject: [PATCH 06/59] Add validation to bot tokens based on string length (#1128) * Add input validation for bot tokens based on their length * Add token validation to BaseDiscordClient#LoginAsync Adds a TokenUtils class which is used to validate that tokens are correct * Revert changes to DiscordRestApiClient * Add Unit tests to the TokenUtils class, fix a logic error that was caught by those tests * Allow for API to throw exceptions Moves the validation of tokens to be inside of LoginInternalAsync, and writes a Warning to the console when the supplied tokens are invalid --- src/Discord.Net.Core/Utils/TokenUtils.cs | 46 ++++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 19 +++- test/Discord.Net.Tests/Tests.TokenUtils.cs | 124 +++++++++++++++++++++ 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Core/Utils/TokenUtils.cs create mode 100644 test/Discord.Net.Tests/Tests.TokenUtils.cs diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs new file mode 100644 index 000000000..2cc0f1041 --- /dev/null +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public static class TokenUtils + { + /// + /// Checks the validity of the supplied token of a specific type. + /// + /// The type of token to validate. + /// The token value to validate. + /// Thrown when the supplied token string is null, empty, or contains only whitespace. + /// Thrown when the supplied TokenType or token value is invalid. + public static void ValidateToken(TokenType tokenType, string token) + { + // A Null or WhiteSpace token of any type is invalid. + if (string.IsNullOrWhiteSpace(token)) + throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token)); + + switch (tokenType) + { + case TokenType.Webhook: + // no validation is performed on Webhook tokens + break; + case TokenType.Bearer: + // no validation is performed on Bearer tokens + break; + case TokenType.Bot: + // bot tokens are assumed to be at least 59 characters in length + // this value was determined by referencing examples in the discord documentation, and by comparing with + // pre-existing tokens + if (token.Length < 59) + throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token)); + break; + default: + // All unrecognized TokenTypes (including User tokens) are considered to be invalid. + throw new ArgumentException("Unrecognized TokenType.", nameof(token)); + } + } + + } +} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index f8642b96c..8ac4e9f98 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -55,11 +55,11 @@ namespace Discord.Rest await _stateLock.WaitAsync().ConfigureAwait(false); try { - await LoginInternalAsync(tokenType, token).ConfigureAwait(false); + await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); } finally { _stateLock.Release(); } } - private async Task LoginInternalAsync(TokenType tokenType, string token) + private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) { if (_isFirstLogin) { @@ -73,6 +73,21 @@ namespace Discord.Rest try { + // If token validation is enabled, validate the token and let it throw any ArgumentExceptions + // that result from invalid parameters + if (validateToken) + { + try + { + TokenUtils.ValidateToken(tokenType, token); + } + catch (ArgumentException ex) + { + // log these ArgumentExceptions and allow for the client to attempt to log in anyways + await LogManager.WarningAsync("Discord", "A supplied token was invalid", ex).ConfigureAwait(false); + } + } + await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; diff --git a/test/Discord.Net.Tests/Tests.TokenUtils.cs b/test/Discord.Net.Tests/Tests.TokenUtils.cs new file mode 100644 index 000000000..dc5a93e34 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.TokenUtils.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Discord +{ + public class TokenUtilsTests + { + /// + /// Tests the usage of + /// to see that when a null, empty or whitespace-only string is passed as the token, + /// it will throw an ArgumentNullException. + /// + [Theory] + [InlineData(null)] + [InlineData("")] // string.Empty isn't a constant type + [InlineData(" ")] + [InlineData(" ")] + [InlineData("\t")] + public void TestNullOrWhitespaceToken(string token) + { + // an ArgumentNullException should be thrown, regardless of the TokenType + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bearer, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Webhook, token)); + } + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [Theory] + [InlineData("123123123")] + // bot token + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + // bearer token taken from discord docs + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestWebhookTokenDoesNotThrowExceptions(string token) + { + TokenUtils.ValidateToken(TokenType.Webhook, token); + } + + // No tests for invalid webhook token behavior, because there is nothing there yet. + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [Theory] + [InlineData("123123123")] + // bot token + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + // bearer token taken from discord docs + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestBearerTokenDoesNotThrowExceptions(string token) + { + TokenUtils.ValidateToken(TokenType.Bearer, token); + } + + // No tests for invalid bearer token behavior, because there is nothing there yet. + + /// + /// Tests the behavior of + /// to see that valid Bot tokens do not throw Exceptions. + /// Valid Bot tokens can be strings of length 59 or above. + /// + [Theory] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + [InlineData("This appears to be completely invalid, however the current validation rules are not very strict.")] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] + public void TestBotTokenDoesNotThrowExceptions(string token) + { + // This example token is pulled from the Discord Docs + // https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header + // should not throw any exception + TokenUtils.ValidateToken(TokenType.Bot, token); + } + + /// + /// Tests the usage of with + /// a Bot token that is invalid. + /// + [Theory] + [InlineData("This is invalid")] + // missing a single character from the end + [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")] + // bearer token + [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] + // client secret + [InlineData("937it3ow87i4ery69876wqire")] + public void TestBotTokenInvalidThrowsArgumentException(string token) + { + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + } + + /// + /// Tests the behavior of + /// to see that an is thrown when an invalid + /// is supplied as a parameter. + /// + /// + /// The type is treated as an invalid . + /// + [Theory] + // TokenType.User + [InlineData(0)] + // out of range TokenType + [InlineData(4)] + [InlineData(7)] + public void TestUnrecognizedTokenType(int type) + { + Assert.Throws(() => + TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")); + } + } +} From a2d8800115c7cc41d82871a68cd08fcdecc747ab Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Thu, 30 Aug 2018 23:29:05 +0200 Subject: [PATCH 07/59] Add == support on Color (#1126) --- src/Discord.Net.Core/Entities/Roles/Color.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 0bb04d339..727049dcc 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -100,6 +100,17 @@ namespace Discord (uint)(b * 255.0f); } + public static bool operator ==(Color lhs, Color rhs) + => lhs.RawValue == rhs.RawValue; + + public static bool operator !=(Color lhs, Color rhs) + => lhs.RawValue != rhs.RawValue; + + public override bool Equals(object obj) + => (obj is Color c && RawValue == c.RawValue); + + public override int GetHashCode() => RawValue.GetHashCode(); + #if NETSTANDARD2_0 || NET45 public static implicit operator StandardColor(Color color) => StandardColor.FromArgb((int)color.RawValue); From 82cfdffc6516e17ea86013e550060bb6df59a016 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Fri, 31 Aug 2018 05:36:44 +0800 Subject: [PATCH 08/59] Add various optimizations and cleanups (#1114) * Change all Select(... as ...) to OfType * Add changes according to https://github.com/Still34/Discord.Net/commit/194a8aa4273caa4ea6723e6fab4d8b3f9efc0a00 --- .../Attributes/OverrideTypeReaderAttribute.cs | 4 ++-- .../Builders/ModuleClassBuilder.cs | 6 ++--- src/Discord.Net.Commands/CommandService.cs | 4 ++-- src/Discord.Net.Commands/Info/CommandInfo.cs | 4 ++-- src/Discord.Net.Commands/Map/CommandMap.cs | 4 ++-- .../Map/CommandMapNode.cs | 11 +++++----- src/Discord.Net.Commands/PrimitiveParsers.cs | 8 +++---- .../Readers/TimeSpanTypeReader.cs | 5 ++--- .../Readers/UserTypeReader.cs | 4 ++-- .../Utilities/ReflectionUtils.cs | 4 ++-- .../Entities/Messages/EmbedBuilder.cs | 4 ++-- .../Entities/Messages/EmbedImage.cs | 4 ++-- .../Entities/Messages/EmbedThumbnail.cs | 4 ++-- .../Entities/Messages/EmbedVideo.cs | 4 ++-- .../Extensions/DiscordClientExtensions.cs | 4 ++-- src/Discord.Net.Core/Format.cs | 4 ++-- src/Discord.Net.Core/Utils/MentionUtils.cs | 18 +++++++-------- .../Utils/Paging/PagedEnumerator.cs | 6 ++--- src/Discord.Net.Core/Utils/Permissions.cs | 10 ++++----- src/Discord.Net.Core/Utils/Preconditions.cs | 4 ++-- src/Discord.Net.Rest/BaseDiscordClient.cs | 2 +- src/Discord.Net.Rest/ClientHelper.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 ++--- .../Entities/Channels/RestDMChannel.cs | 2 +- .../Entities/Channels/RestGroupChannel.cs | 2 +- .../Entities/Channels/RestGuildChannel.cs | 2 +- .../Entities/Guilds/RestGuild.cs | 6 ++--- .../Entities/Guilds/RestUserGuild.cs | 4 ++-- .../Entities/Users/RestGuildUser.cs | 4 ++-- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 4 ++-- .../Net/Queue/ClientBucket.cs | 14 ++++++------ .../Net/Queue/RequestQueue.cs | 6 ++--- src/Discord.Net.Rest/Net/RateLimitInfo.cs | 2 +- .../Audio/AudioClient.cs | 18 +++++++-------- .../Audio/Streams/BufferedWriteStream.cs | 10 ++++----- src/Discord.Net.WebSocket/ClientState.cs | 4 ++-- .../DiscordShardedClient.cs | 8 +++---- .../DiscordSocketApiClient.cs | 2 +- .../DiscordSocketClient.cs | 22 +++++++++---------- .../DiscordVoiceApiClient.cs | 4 ++-- .../Entities/Channels/SocketDMChannel.cs | 2 +- .../Entities/Channels/SocketGroupChannel.cs | 6 ++--- .../Entities/Guilds/SocketGuild.cs | 10 ++++----- .../Entities/Messages/MessageCache.cs | 4 ++-- .../Entities/Messages/SocketUserMessage.cs | 2 +- .../Net/DefaultWebSocketClient.cs | 2 +- 46 files changed, 131 insertions(+), 135 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 44ab6d214..17e2310d4 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -7,13 +7,13 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class OverrideTypeReaderAttribute : Attribute { - private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); + private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); public Type TypeReader { get; } public OverrideTypeReaderAttribute(Type overridenTypeReader) { - if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) + if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); TypeReader = overridenTypeReader; diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cbe02aafb..307874ca6 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -10,7 +10,7 @@ namespace Discord.Commands { internal static class ModuleClassBuilder { - private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); + private static readonly TypeInfo ModuleTypeInfo = typeof(IModuleBase).GetTypeInfo(); public static async Task> SearchAsync(Assembly assembly, CommandService service) { @@ -135,7 +135,7 @@ namespace Discord.Commands if (builder.Name == null) builder.Name = typeInfo.Name; - var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); + var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition); foreach (var method in validCommands) { @@ -299,7 +299,7 @@ namespace Discord.Commands private static bool IsValidModuleDefinition(TypeInfo typeInfo) { - return _moduleTypeInfo.IsAssignableFrom(typeInfo) && + return ModuleTypeInfo.IsAssignableFrom(typeInfo) && !typeInfo.IsAbstract && !typeInfo.ContainsGenericParameters; } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 6fd5d38ad..7b7cffda2 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -118,7 +118,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (_typedModuleDefs.ContainsKey(type)) - throw new ArgumentException($"This module has already been added."); + throw new ArgumentException("This module has already been added."); var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); @@ -241,7 +241,7 @@ namespace Discord.Commands { if (_defaultTypeReaders.ContainsKey(type)) _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + - $"To suppress this message, use AddTypeReader(reader, true)."); + "To suppress this message, use AddTypeReader(reader, true)."); AddTypeReader(type, reader, true); } /// diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index df0bb297b..604bfbc93 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -1,4 +1,4 @@ -using Discord.Commands.Builders; +using Discord.Commands.Builders; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -63,7 +63,7 @@ namespace Discord.Commands Attributes = builder.Attributes.ToImmutableArray(); Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; + HasVarArgs = builder.Parameters.Count > 0 && builder.Parameters[builder.Parameters.Count - 1].IsMultiple; IgnoreExtraArgs = builder.IgnoreExtraArgs; _action = builder.Callback; diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index bcff800d3..141ec6fdf 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Discord.Commands { @@ -6,7 +6,7 @@ namespace Discord.Commands { private readonly CommandService _service; private readonly CommandMapNode _root; - private static readonly string[] _blankAliases = new[] { "" }; + private static readonly string[] BlankAliases = { "" }; public CommandMap(CommandService service) { diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 863409207..bd3067718 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -7,7 +7,7 @@ namespace Discord.Commands { internal class CommandMapNode { - private static readonly char[] _whitespaceChars = new[] { ' ', '\r', '\n' }; + private static readonly char[] WhitespaceChars = { ' ', '\r', '\n' }; private readonly ConcurrentDictionary _nodes; private readonly string _name; @@ -52,7 +52,6 @@ namespace Discord.Commands public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) { int nextSegment = NextSegment(text, index, service._separatorChar); - string name; lock (_lockObj) { @@ -60,13 +59,13 @@ namespace Discord.Commands _commands = _commands.Remove(command); else { + string name; if (nextSegment == -1) name = text.Substring(index); else name = text.Substring(index, nextSegment - index); - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) + if (_nodes.TryGetValue(name, out var nextNode)) { nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); if (nextNode.IsEmpty) @@ -100,7 +99,7 @@ namespace Discord.Commands } //Check if this is the last command segment before args - nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); + nextSegment = NextSegment(text, index, WhitespaceChars, service._separatorChar); if (nextSegment != -1) { name = text.Substring(index, nextSegment - index); diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index bf0622c28..e9b6aac3f 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -8,9 +8,9 @@ namespace Discord.Commands internal static class PrimitiveParsers { - private static readonly Lazy> _parsers = new Lazy>(CreateParsers); + private static readonly Lazy> Parsers = new Lazy>(CreateParsers); - public static IEnumerable SupportedTypes = _parsers.Value.Keys; + public static IEnumerable SupportedTypes = Parsers.Value.Keys; static IReadOnlyDictionary CreateParsers() { @@ -34,7 +34,7 @@ namespace Discord.Commands return parserBuilder.ToImmutable(); } - public static TryParseDelegate Get() => (TryParseDelegate)_parsers.Value[typeof(T)]; - public static Delegate Get(Type type) => _parsers.Value[type]; + public static TryParseDelegate Get() => (TryParseDelegate)Parsers.Value[typeof(T)]; + public static Delegate Get(Type type) => Parsers.Value[type]; } } diff --git a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs index 31ab9d821..314fbb322 100644 --- a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -6,8 +6,7 @@ namespace Discord.Commands { internal class TimeSpanTypeReader : TypeReader { - private static readonly string[] _formats = new[] - { + private static readonly string[] Formats = { "%d'd'%h'h'%m'm'%s's'", //4d3h2m1s "%d'd'%h'h'%m'm'", //4d3h2m "%d'd'%h'h'%s's'", //4d3h 1s @@ -27,7 +26,7 @@ namespace Discord.Commands public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - return (TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan)) + return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) ? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 425c2ccb7..498a214e4 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -71,8 +71,8 @@ namespace Discord.Commands .Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase)) .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) - AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); + foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) + AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); } if (results.Count > 0) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 30dd7c36b..ec981cf52 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -8,7 +8,7 @@ namespace Discord.Commands { internal static class ReflectionUtils { - private static readonly TypeInfo _objectTypeInfo = typeof(object).GetTypeInfo(); + private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo(); internal static T CreateObject(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) => CreateBuilder(typeInfo, commands)(services); @@ -54,7 +54,7 @@ namespace Discord.Commands private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) { var result = new List(); - while (ownerType != _objectTypeInfo) + while (ownerType != ObjectTypeInfo) { foreach (var prop in ownerType.DeclaredProperties) { diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 04f4f6884..7e288ef6b 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -239,7 +239,7 @@ namespace Discord get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); _name = value; } @@ -251,7 +251,7 @@ namespace Discord set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _value = stringValue; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index f21d42c0c..e12ffc53f 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -20,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + 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 209a93e37..9b3d6153a 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -20,6 +20,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + 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 f00681d89..5725e0e14 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; namespace Discord @@ -18,6 +18,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs index ff3c7caf7..81cd10b49 100644 --- a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs +++ b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs @@ -12,12 +12,12 @@ namespace Discord public static async Task GetDMChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; public static async Task> GetDMChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); public static async Task GetGroupChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; public static async Task> GetGroupChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); public static async Task GetOptimalVoiceRegionAsync(this IDiscordClient discord) { diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index aa822f99e..414e00a29 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,9 +1,9 @@ -namespace Discord +namespace Discord { public static class Format { // Characters which need escaping - private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; + private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 6c69827b4..f2afe497a 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Text; @@ -139,22 +139,22 @@ namespace Discord if (user != null) return $"@{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: if (user != null) return $"{guildUser?.Nickname ?? user?.Username}"; else - return $""; + return ""; case TagHandling.FullName: if (user != null) return $"@{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.FullNameNoPrefix: if (user != null) return $"{user.Username}#{user.Discriminator}"; else - return $""; + return ""; case TagHandling.Sanitize: if (guildUser != null && guildUser.Nickname == null) return MentionUser($"{SanitizeChar}{tag.Key}", false); @@ -176,13 +176,13 @@ namespace Discord if (channel != null) return $"#{channel.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (channel != null) return $"{channel.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionChannel($"{SanitizeChar}{tag.Key}"); } @@ -201,13 +201,13 @@ namespace Discord if (role != null) return $"@{role.Name}"; else - return $""; + return ""; case TagHandling.NameNoPrefix: case TagHandling.FullNameNoPrefix: if (role != null) return $"{role.Name}"; else - return $""; + return ""; case TagHandling.Sanitize: return MentionRole($"{SanitizeChar}{tag.Key}"); } diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index 96059bb4f..a31721875 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -60,7 +60,7 @@ namespace Discord if (Current.Count == 0) _info.Remaining = 0; } - _info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; + _info.PageSize = _info.Remaining != null ? Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; if (_info.Remaining != 0) { @@ -74,4 +74,4 @@ namespace Discord public void Dispose() { Current = null; } } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 04e6784c3..6e7125ab7 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace Discord { @@ -119,13 +119,11 @@ namespace Discord resolvedPermissions = mask; //Owners and administrators always have all permissions else { - OverwritePermissions? perms; - //Start with this user's guild permissions resolvedPermissions = guildPermissions; //Give/Take Everyone permissions - perms = channel.GetPermissionOverwrite(guild.EveryoneRole); + var perms = channel.GetPermissionOverwrite(guild.EveryoneRole); if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; @@ -133,7 +131,7 @@ namespace Discord ulong deniedPermissions = 0UL, allowedPermissions = 0UL; foreach (var roleId in user.RoleIds) { - IRole role = null; + IRole role; if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) { perms = channel.GetPermissionOverwrite(role); @@ -151,7 +149,7 @@ namespace Discord if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; - if (channel is ITextChannel textChannel) + if (channel is ITextChannel) { if (!GetValue(resolvedPermissions, ChannelPermission.ViewChannel)) { diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 300f584e4..1a297a6a1 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Discord { @@ -198,7 +198,7 @@ namespace Discord for (var i = 0; i < roles.Length; i++) { if (roles[i] == guildId) - throw new ArgumentException($"The everyone role cannot be assigned to a user", name); + throw new ArgumentException("The everyone role cannot be assigned to a user", name); } } } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 8ac4e9f98..8a3db3e6a 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -92,7 +92,7 @@ namespace Discord.Rest await OnLoginAsync(tokenType, token).ConfigureAwait(false); LoginState = LoginState.LoggedIn; } - catch (Exception) + catch { await LogoutInternalAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index d8f481d15..aa99fe005 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -47,7 +47,7 @@ namespace Discord.Rest public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); - return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); + return models.Select(RestConnection.Create).ToImmutableArray(); } public static async Task GetInviteAsync(BaseDiscordClient client, diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index a9fc1ed74..f8e3e6f50 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -125,7 +125,7 @@ namespace Discord.API LoginState = LoginState.LoggedIn; } - catch (Exception) + catch { await LogoutInternalAsync().ConfigureAwait(false); throw; @@ -1394,9 +1394,9 @@ namespace Discord.API int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); string fieldName = GetFieldName(methodArgs[argId + 1]); - int? mappedId; - mappedId = BucketIds.GetIndex(fieldName); + var mappedId = BucketIds.GetIndex(fieldName); + if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 64efcf24b..20a76aaf0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable + public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel { public RestUser CurrentUser { get; private set; } public RestUser Recipient { get; private set; } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 5ac0f2c40..c6b5d6ad0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -11,7 +11,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable + public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel { private string _iconId; private ImmutableDictionary _users; diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 7355e3673..1dbcf7e9a 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -7,7 +7,7 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable + public class RestGuildChannel : RestChannel, IGuildChannel { private ImmutableArray _overwrites; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 9bf13fa07..e6819e8a4 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -168,7 +168,7 @@ namespace Discord.Rest public async Task> GetTextChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestTextChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task GetVoiceChannelAsync(ulong id, RequestOptions options = null) { @@ -178,12 +178,12 @@ namespace Discord.Rest public async Task> GetVoiceChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestVoiceChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task> GetCategoryChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); - return channels.Select(x => x as RestCategoryChannel).Where(x => x != null).ToImmutableArray(); + return channels.OfType().ToImmutableArray(); } public async Task GetAFKChannelAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index 6bc9cea7a..de5a5f7d9 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using Model = Discord.API.UserGuild; @@ -6,7 +6,7 @@ using Model = Discord.API.UserGuild; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestUserGuild : RestEntity, ISnowflakeEntity, IUserGuild + public class RestUserGuild : RestEntity, IUserGuild { private string _iconId; diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index e571f8f73..3c47587cb 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -9,7 +9,7 @@ using Model = Discord.API.GuildMember; namespace Discord.Rest { [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGuildUser : RestUser, IGuildUser, IUpdateable + public class RestGuildUser : RestUser, IGuildUser { private long? _joinedAtTicks; private ImmutableArray _roleIds; diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index ec789be59..54fe3b681 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -131,14 +131,14 @@ namespace Discord.Net.Rest return new RestResponse(response.StatusCode, headers, stream); } - private static readonly HttpMethod _patch = new HttpMethod("PATCH"); + private static readonly HttpMethod Patch = new HttpMethod("PATCH"); private HttpMethod GetMethod(string method) { switch (method) { case "DELETE": return HttpMethod.Delete; case "GET": return HttpMethod.Get; - case "PATCH": return _patch; + case "PATCH": return Patch; case "POST": return HttpMethod.Post; case "PUT": return HttpMethod.Put; default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"); diff --git a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs index f32df1bcf..cd9d8aa54 100644 --- a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; namespace Discord.Net.Queue { @@ -9,8 +9,8 @@ namespace Discord.Net.Queue } internal struct ClientBucket { - private static readonly ImmutableDictionary _defsByType; - private static readonly ImmutableDictionary _defsById; + private static readonly ImmutableDictionary DefsByType; + private static readonly ImmutableDictionary DefsById; static ClientBucket() { @@ -23,16 +23,16 @@ namespace Discord.Net.Queue var builder = ImmutableDictionary.CreateBuilder(); foreach (var bucket in buckets) builder.Add(bucket.Type, bucket); - _defsByType = builder.ToImmutable(); + DefsByType = builder.ToImmutable(); var builder2 = ImmutableDictionary.CreateBuilder(); foreach (var bucket in buckets) builder2.Add(bucket.Id, bucket); - _defsById = builder2.ToImmutable(); + DefsById = builder2.ToImmutable(); } - public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; - public static ClientBucket Get(string id) => _defsById[id]; + public static ClientBucket Get(ClientBucketType type) => DefsByType[type]; + public static ClientBucket Get(string id) => DefsById[id]; public ClientBucketType Type { get; } public string Id { get; } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 943b76359..40b91e98e 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; #if DEBUG_LIMITS using System.Diagnostics; @@ -16,10 +16,10 @@ namespace Discord.Net.Queue private readonly ConcurrentDictionary _buckets; private readonly SemaphoreSlim _tokenLock; + private readonly CancellationTokenSource _cancelToken; //Dispose token private CancellationTokenSource _clearToken; private CancellationToken _parentToken; private CancellationToken _requestCancelToken; //Parent token + Clear token - private CancellationTokenSource _cancelToken; //Dispose token private DateTimeOffset _waitUntil; private Task _cleanupTask; @@ -115,7 +115,7 @@ namespace Discord.Net.Queue foreach (var bucket in _buckets.Select(x => x.Value)) { if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) - _buckets.TryRemove(bucket.Id, out RequestBucket ignored); + _buckets.TryRemove(bucket.Id, out _); } await Task.Delay(60000, _cancelToken.Token); //Runs each minute } diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index a517f290c..d31cc5cdd 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -15,7 +15,7 @@ namespace Discord.Net internal RateLimitInfo(Dictionary headers) { IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && - bool.TryParse(temp, out var isGlobal) ? isGlobal : false; + bool.TryParse(temp, out var isGlobal) && isGlobal; Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && int.TryParse(temp, out var limit) ? limit : (int?)null; Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index bbb65a298..9f197b5c5 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace Discord.Audio { //TODO: Add audio reconnecting - internal partial class AudioClient : IAudioClient, IDisposable + internal partial class AudioClient : IAudioClient { internal struct StreamPair { @@ -65,7 +65,7 @@ namespace Discord.Audio ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); - ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); + ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedPacket += ProcessPacketAsync; @@ -131,7 +131,7 @@ namespace Discord.Audio await keepaliveTask.ConfigureAwait(false); _keepaliveTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out _)) { } _lastMessageTime = 0; await ClearInputStreamsAsync().ConfigureAwait(false); @@ -292,7 +292,7 @@ namespace Discord.Audio { if (packet.Length != 70) { - await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); return; } string ip; @@ -304,7 +304,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); return; } @@ -331,7 +331,7 @@ namespace Discord.Audio { if (pair.Key == value) { - int latency = (int)(Environment.TickCount - pair.Value); + int latency = Environment.TickCount - pair.Value; int before = UdpLatency; UdpLatency = latency; @@ -344,7 +344,7 @@ namespace Discord.Audio { if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) { - await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); return; } if (!_ssrcMap.TryGetValue(ssrc, out var userId)) @@ -363,7 +363,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); return; } //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); @@ -372,7 +372,7 @@ namespace Discord.Audio } catch (Exception ex) { - await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); + await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); return; } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index fb302f132..47a7e2809 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -116,7 +116,7 @@ namespace Discord.Audio.Streams timestamp += OpusEncoder.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync($"Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer underrun"); #endif } } @@ -140,7 +140,7 @@ namespace Discord.Audio.Streams if (!_bufferPool.TryDequeue(out byte[] buffer)) { #if DEBUG - var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock + var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock #endif return; } @@ -149,7 +149,7 @@ namespace Discord.Audio.Streams if (!_isPreloaded && _queuedFrames.Count == _queueLength) { #if DEBUG - var _ = _logger?.DebugAsync($"Preloaded"); + var _ = _logger?.DebugAsync("Preloaded"); #endif _isPreloaded = true; } @@ -169,8 +169,8 @@ namespace Discord.Audio.Streams { do cancelToken.ThrowIfCancellationRequested(); - while (_queuedFrames.TryDequeue(out Frame ignored)); + while (_queuedFrames.TryDequeue(out _)); return Task.Delay(0); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f07976a0a..a119e5e28 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -72,7 +72,7 @@ namespace Discord.WebSocket switch (channel) { case SocketDMChannel dmChannel: - _dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); + _dmChannels.TryRemove(dmChannel.Recipient.Id, out _); break; case SocketGroupChannel groupChannel: _groupChannels.TryRemove(id); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index e29cc4057..881ce3909 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -13,11 +13,11 @@ namespace Discord.WebSocket { private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; + private readonly Dictionary _shardIdsToIndex; + private readonly bool _automaticShards; private int[] _shardIds; - private Dictionary _shardIdsToIndex; private DiscordSocketClient[] _shards; private int _totalShards; - private bool _automaticShards; /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. public override int Latency { get => GetLatency(); protected set { } } @@ -25,8 +25,8 @@ namespace Discord.WebSocket public override IActivity Activity { get => _shards[0].Activity; protected set { } } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); - public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); + public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); + public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); public IReadOnlyCollection Shards => _shards; public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 8ae41cc59..d42bd7132 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -26,9 +26,9 @@ namespace Discord.API public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly bool _isExplicitUrl; private CancellationTokenSource _connectCancelToken; private string _gatewayUrl; - private bool _isExplicitUrl; //Store our decompression streams for zlib shared state private MemoryStream _compressed; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 9efc7d3fa..45d60b764 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -63,9 +63,9 @@ namespace Discord.WebSocket public override IReadOnlyCollection Guilds => State.Guilds; public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; public IReadOnlyCollection DMChannels - => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); public IReadOnlyCollection GroupChannels - => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); /// Creates a new REST/WebSocket discord client. @@ -207,7 +207,7 @@ namespace Discord.WebSocket await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out _)) { } _lastMessageTime = 0; await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); @@ -218,7 +218,7 @@ namespace Discord.WebSocket //Clear large guild queue await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); - while (_largeGuilds.TryDequeue(out ulong guildId)) { } + while (_largeGuilds.TryDequeue(out _)) { } //Raise virtual GUILD_UNAVAILABLEs await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); @@ -351,7 +351,7 @@ namespace Discord.WebSocket var gameModel = new GameModel(); // Discord only accepts rich presence over RPC, don't even bother building a payload - if (Activity is RichGame game) + if (Activity is RichGame) throw new NotSupportedException("Outgoing Rich Presences are not supported"); if (Activity != null) @@ -508,7 +508,7 @@ namespace Discord.WebSocket { type = "GUILD_AVAILABLE"; _lastGuildAvailableTime = Environment.TickCount; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -533,7 +533,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); var guild = AddGuild(data, State); if (guild != null) @@ -614,7 +614,7 @@ namespace Discord.WebSocket if (data.Unavailable == true) { type = "GUILD_UNAVAILABLE"; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); var guild = State.GetGuild(data.Id); if (guild != null) @@ -630,7 +630,7 @@ namespace Discord.WebSocket } else { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); var guild = RemoveGuild(data.Id); if (guild != null) @@ -1626,7 +1626,7 @@ namespace Discord.WebSocket var guild = State.RemoveGuild(id); if (guild != null) { - foreach (var channel in guild.Channels) + foreach (var _ in guild.Channels) State.RemoveChannel(id); foreach (var user in guild.Users) user.GlobalUser.RemoveRef(this); @@ -1679,7 +1679,7 @@ namespace Discord.WebSocket if (eventHandler.HasSubscribers) { if (HandlerTimeout.HasValue) - await TimeoutWrap(name, () => eventHandler.InvokeAsync()).ConfigureAwait(false); + await TimeoutWrap(name, eventHandler.InvokeAsync).ConfigureAwait(false); else await eventHandler.InvokeAsync().ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 0eb92caed..80dec0fd4 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -39,8 +39,8 @@ namespace Discord.Audio private readonly JsonSerializer _serializer; private readonly SemaphoreSlim _connectionLock; + private readonly IUdpSocket _udp; private CancellationTokenSource _connectCancelToken; - private IUdpSocket _udp; private bool _isDisposed; private ulong _nextKeepalive; @@ -188,7 +188,7 @@ namespace Discord.Audio ConnectionState = ConnectionState.Connected; } - catch (Exception) + catch { await DisconnectInternalAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 11b7ce25a..9a8be9703 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -97,7 +97,7 @@ namespace Discord.WebSocket if (id == Recipient.Id) return Recipient; else if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser as SocketSelfUser; + return Discord.CurrentUser; else return null; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 125625456..74a1e3725 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -18,10 +18,10 @@ namespace Discord.WebSocket public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { private readonly MessageCache _messages; + private readonly ConcurrentDictionary _voiceStates; private string _iconId; private ConcurrentDictionary _users; - private ConcurrentDictionary _voiceStates; public string Name { get; private set; } @@ -129,7 +129,7 @@ namespace Discord.WebSocket internal SocketGroupUser GetOrAddUser(UserModel model) { if (_users.TryGetValue(model.Id, out SocketGroupUser user)) - return user as SocketGroupUser; + return user; else { var privateUser = SocketGroupUser.Create(this, Discord.State, model); @@ -143,7 +143,7 @@ namespace Discord.WebSocket if (_users.TryRemove(id, out SocketGroupUser user)) { user.GlobalUser.RemoveRef(Discord); - return user as SocketGroupUser; + return user; } return null; } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index bf33ef569..78ea4004a 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -91,11 +91,11 @@ namespace Discord.WebSocket } } public IReadOnlyCollection TextChannels - => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public IReadOnlyCollection VoiceChannels - => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public IReadOnlyCollection CategoryChannels - => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; public SocketRole EveryoneRole => GetRole(Id); public IReadOnlyCollection Channels @@ -563,7 +563,7 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); } - catch (Exception) + catch { await DisconnectAudioInternalAsync().ConfigureAwait(false); throw; @@ -580,7 +580,7 @@ namespace Discord.WebSocket throw new TimeoutException(); return await promise.Task.ConfigureAwait(false); } - catch (Exception) + catch { await DisconnectAudioAsync().ConfigureAwait(false); throw; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index c2cad4d86..6ebd0fa1c 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -28,7 +28,7 @@ namespace Discord.WebSocket _orderedMessages.Enqueue(message.Id); while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) - _messages.TryRemove(msgId, out SocketMessage msg); + _messages.TryRemove(msgId, out _); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index f8c15a986..bf817e008 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -12,12 +12,12 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { + private readonly List _reactions = new List(); private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - private List _reactions = new List(); public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index c60368da0..dc5201ac1 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -22,8 +22,8 @@ namespace Discord.Net.WebSockets private readonly SemaphoreSlim _lock; private readonly Dictionary _headers; + private readonly IWebProxy _proxy; private ClientWebSocket _client; - private IWebProxy _proxy; private Task _task; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; From 272604f27578dba67de8039e4e88901a26299883 Mon Sep 17 00:00:00 2001 From: Alex Gravely Date: Thu, 30 Aug 2018 17:38:44 -0400 Subject: [PATCH 09/59] Make voice connection properties optional. (#1129) --- .../Entities/Channels/SocketVoiceChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index f967e6e6a..8e6e09a9f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -43,7 +43,7 @@ namespace Discord.WebSocket public Task ModifyAsync(Action func, RequestOptions options = null) => ChannelHelper.ModifyAsync(this, Discord, func, options); - public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) + public async Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) { return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); } From 9e9a11d4b09362aadac6d32be0ac83fdddc5e19e Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Thu, 6 Sep 2018 19:08:45 -0700 Subject: [PATCH 10/59] Fix swapped parameters in ArgumentException, ArgumentNullException cstrs (#1139) * Fix swapped parameters in ArgumentException and ArgumentNullException cstrs The constructors of ArgumentException and ArgumentNullException are inconsistent with each other, which results in their parameters being swapped here and there. * Use named parameters for ArgumentException constructors Cleans up some of the methods of EmbedBuilder to use simpler syntax as well --- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 4 +- .../Entities/Messages/EmbedBuilder.cs | 34 ++++++++--------- .../Permissions/ChannelPermissions.cs | 2 +- .../Utils/ConcurrentHashSet.cs | 24 ++++++------ src/Discord.Net.Core/Utils/MentionUtils.cs | 6 +-- src/Discord.Net.Core/Utils/Preconditions.cs | 38 +++++-------------- src/Discord.Net.Core/Utils/TokenUtils.cs | 6 +-- src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 +-- .../Entities/Guilds/GuildHelper.cs | 10 ++--- 9 files changed, 56 insertions(+), 74 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index e3a228c83..a71a73327 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; namespace Discord @@ -58,7 +58,7 @@ namespace Discord { if (TryParse(text, out Emote result)) return result; - throw new ArgumentException("Invalid emote format", nameof(text)); + throw new ArgumentException(message: "Invalid emote format", paramName: nameof(text)); } public static bool TryParse(string text, out Emote result) diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 7e288ef6b..82e94e39f 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -29,7 +29,7 @@ namespace Discord get => _title; set { - if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); + if (value?.Length > MaxTitleLength) throw new ArgumentException(message: $"Title length must be less than or equal to {MaxTitleLength}.", paramName: nameof(Title)); _title = value; } } @@ -38,7 +38,7 @@ namespace Discord get => _description; set { - if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); + if (value?.Length > MaxDescriptionLength) throw new ArgumentException(message: $"Description length must be less than or equal to {MaxDescriptionLength}.", paramName: nameof(Description)); _description = value; } } @@ -48,7 +48,7 @@ namespace Discord get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(Url)); _url = value; } } @@ -57,7 +57,7 @@ namespace Discord get => _thumbnail?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(ThumbnailUrl)); _thumbnail = new EmbedThumbnail(value, null, null, null); } } @@ -66,7 +66,7 @@ namespace Discord get => _image?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(ImageUrl)); _image = new EmbedImage(value, null, null, null); } } @@ -75,8 +75,8 @@ namespace Discord get => _fields; set { - if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); - if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); + if (value == null) throw new ArgumentNullException(paramName: nameof(Fields), message: "Cannot set an embed builder's fields collection to null"); + if (value.Count > MaxFieldCount) throw new ArgumentException(message: $"Field count must be less than or equal to {MaxFieldCount}.", paramName: nameof(Fields)); _fields = value; } } @@ -200,7 +200,7 @@ namespace Discord { if (Fields.Count >= MaxFieldCount) { - throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(field)); + throw new ArgumentException(message: $"Field count must be less than or equal to {MaxFieldCount}.", paramName: nameof(field)); } Fields.Add(field); @@ -239,8 +239,8 @@ namespace Discord get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); - if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(message: "Field name must not be null, empty or entirely whitespace.", paramName: nameof(Name)); + if (value.Length > MaxFieldNameLength) throw new ArgumentException(message: $"Field name length must be less than or equal to {MaxFieldNameLength}.", paramName: nameof(Name)); _name = value; } } @@ -251,8 +251,8 @@ namespace Discord set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); - if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException(message: "Field value must not be null or empty.", paramName: nameof(Value)); + if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException(message: $"Field value length must be less than or equal to {MaxFieldValueLength}.", paramName: nameof(Value)); _value = stringValue; } } @@ -290,7 +290,7 @@ namespace Discord get => _name; set { - if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); + if (value?.Length > MaxAuthorNameLength) throw new ArgumentException(message: $"Author name length must be less than or equal to {MaxAuthorNameLength}.", paramName: nameof(Name)); _name = value; } } @@ -299,7 +299,7 @@ namespace Discord get => _url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(Url)); _url = value; } } @@ -308,7 +308,7 @@ namespace Discord get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); _iconUrl = value; } } @@ -345,7 +345,7 @@ namespace Discord get => _text; set { - if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); + if (value?.Length > MaxFooterTextLength) throw new ArgumentException(message: $"Footer text length must be less than or equal to {MaxFooterTextLength}.", paramName: nameof(Text)); _text = value; } } @@ -354,7 +354,7 @@ namespace Discord get => _iconUrl; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); + if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI", paramName: nameof(IconUrl)); _iconUrl = value; } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 61d588f8a..ea56734ff 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -29,7 +29,7 @@ namespace Discord case ICategoryChannel _: return Category; case IDMChannel _: return DM; case IGroupChannel _: return Group; - default: throw new ArgumentException("Unknown channel type", nameof(channel)); + default: throw new ArgumentException(message: "Unknown channel type", paramName: nameof(channel)); } } diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 1fc11587e..e9edfb772 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -160,23 +160,23 @@ namespace Discord public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) : this(comparer) { - if (collection == null) throw new ArgumentNullException(nameof(collection)); + if (collection == null) throw new ArgumentNullException(paramName: nameof(collection)); InitializeFromCollection(collection); } public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) : this(concurrencyLevel, DefaultCapacity, false, comparer) { - if (collection == null) throw new ArgumentNullException(nameof(collection)); - if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + if (collection == null) throw new ArgumentNullException(paramName: nameof(collection)); + if (comparer == null) throw new ArgumentNullException(paramName: nameof(comparer)); InitializeFromCollection(collection); } public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) : this(concurrencyLevel, capacity, false, comparer) { } internal ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) { - if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(paramName: nameof(concurrencyLevel)); + if (capacity < 0) throw new ArgumentOutOfRangeException(paramName: nameof(capacity)); + if (comparer == null) throw new ArgumentNullException(paramName: nameof(comparer)); if (capacity < concurrencyLevel) capacity = concurrencyLevel; @@ -197,7 +197,7 @@ namespace Discord { foreach (var value in collection) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); if (!TryAddInternal(value, _comparer.GetHashCode(value), false)) throw new ArgumentException(); @@ -209,7 +209,7 @@ namespace Discord public bool ContainsKey(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return ContainsKeyInternal(value, _comparer.GetHashCode(value)); } private bool ContainsKeyInternal(T value, int hashcode) @@ -232,7 +232,7 @@ namespace Discord public bool TryAdd(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return TryAddInternal(value, _comparer.GetHashCode(value), true); } private bool TryAddInternal(T value, int hashcode, bool acquireLock) @@ -281,7 +281,7 @@ namespace Discord public bool TryRemove(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException(paramName: "key"); return TryRemoveInternal(value); } private bool TryRemoveInternal(T value) @@ -467,4 +467,4 @@ namespace Discord Monitor.Exit(_tables._locks[i]); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index f2afe497a..a081b5a5a 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -21,7 +21,7 @@ namespace Discord { if (TryParseUser(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided user mention string. public static bool TryParseUser(string text, out ulong userId) @@ -45,7 +45,7 @@ namespace Discord { if (TryParseChannel(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided channel mention string. public static bool TryParseChannel(string text, out ulong channelId) @@ -66,7 +66,7 @@ namespace Discord { if (TryParseRole(text, out ulong id)) return id; - throw new ArgumentException("Invalid mention format", nameof(text)); + throw new ArgumentException(message: "Invalid mention format", paramName: nameof(text)); } /// Tries to parse a provided role mention string. public static bool TryParseRole(string text, out ulong roleId) diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 1a297a6a1..6cbf4a173 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -10,8 +10,8 @@ namespace Discord private static ArgumentNullException CreateNotNullException(string name, string msg) { - if (msg == null) return new ArgumentNullException(name); - else return new ArgumentNullException(name, msg); + if (msg == null) return new ArgumentNullException(paramName: name); + else return new ArgumentNullException(paramName: name, message: msg); } //Strings @@ -45,10 +45,7 @@ namespace Discord } private static ArgumentException CreateNotEmptyException(string name, string msg) - { - if (msg == null) return new ArgumentException("Argument cannot be blank", name); - else return new ArgumentException(name, msg); - } + => new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name); //Numerics public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } @@ -85,11 +82,8 @@ namespace Discord 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) return new ArgumentException($"Value may not be equal to {value}", name); - else return new ArgumentException(msg, name); - } - + => new ArgumentException(message: msg ?? $"Value may not be equal to {value}", paramName: 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); } @@ -108,10 +102,7 @@ namespace Discord 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) return new ArgumentException($"Value must be at least {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be at least {value}", paramName: 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); } @@ -131,10 +122,7 @@ namespace Discord 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) return new ArgumentException($"Value must be greater than {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be greater than {value}", paramName: 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); } @@ -154,10 +142,7 @@ namespace Discord 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) return new ArgumentException($"Value must be at most {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be at most {value}", paramName: 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); } @@ -177,10 +162,7 @@ namespace Discord 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) return new ArgumentException($"Value must be less than {value}", name); - else return new ArgumentException(msg, name); - } + => new ArgumentException(message: msg ?? $"Value must be less than {value}", paramName: name); // Bulk Delete public static void YoungerThanTwoWeeks(ulong[] collection, string name) @@ -198,7 +180,7 @@ namespace Discord for (var i = 0; i < roles.Length; i++) { if (roles[i] == guildId) - throw new ArgumentException("The everyone role cannot be assigned to a user", name); + throw new ArgumentException(message: "The everyone role cannot be assigned to a user", paramName: name); } } } diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs index 2cc0f1041..6decdc52f 100644 --- a/src/Discord.Net.Core/Utils/TokenUtils.cs +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -19,7 +19,7 @@ namespace Discord { // A Null or WhiteSpace token of any type is invalid. if (string.IsNullOrWhiteSpace(token)) - throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token)); + throw new ArgumentNullException(paramName: nameof(token), message: "A token cannot be null, empty, or contain only whitespace."); switch (tokenType) { @@ -34,11 +34,11 @@ namespace Discord // this value was determined by referencing examples in the discord documentation, and by comparing with // pre-existing tokens if (token.Length < 59) - throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token)); + throw new ArgumentException(message: "A Bot token must be at least 59 characters in length.", paramName: nameof(token)); break; default: // All unrecognized TokenTypes (including User tokens) are considered to be invalid. - throw new ArgumentException("Unrecognized TokenType.", nameof(token)); + throw new ArgumentException(message: "Unrecognized TokenType.", paramName: nameof(token)); } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f8e3e6f50..02529310f 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -78,7 +78,7 @@ namespace Discord.API case TokenType.Bearer: return $"Bearer {token}"; default: - throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); + throw new ArgumentException(message: "Unknown OAuth token type", paramName: nameof(tokenType)); } } internal virtual void Dispose(bool disposing) @@ -473,7 +473,7 @@ namespace Discord.API 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)); + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); @@ -490,7 +490,7 @@ namespace Discord.API 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)); + throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); options = RequestOptions.CreateOrClone(options); return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 4bd0e9972..3b2935149 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -147,7 +147,7 @@ namespace Discord.Rest public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var props = new TextChannelProperties(); func?.Invoke(props); @@ -164,7 +164,7 @@ namespace Discord.Rest public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var props = new VoiceChannelProperties(); func?.Invoke(props); @@ -181,7 +181,7 @@ namespace Discord.Rest public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var args = new CreateGuildChannelParams(name, ChannelType.Category); var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); @@ -221,7 +221,7 @@ namespace Discord.Rest public static async Task CreateRoleAsync(IGuild guild, BaseDiscordClient client, string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == null) throw new ArgumentNullException(paramName: nameof(name)); var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, options).ConfigureAwait(false); var role = RestRole.Create(client, guild, model); @@ -356,7 +356,7 @@ namespace Discord.Rest public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, RequestOptions options) { - if (func == null) throw new ArgumentNullException(nameof(func)); + if (func == null) throw new ArgumentNullException(paramName: nameof(func)); var props = new EmoteProperties(); func(props); From 97d17cfdda22a04173b63247c646acc7cd1122a7 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 11 Sep 2018 18:21:22 -0400 Subject: [PATCH 11/59] api: add slow mode This adds the following property to ITextChannel - SlowMode (int) The SlowMode field defines the time in seconds users must wait between sending messages; if zero, slow-mode is disabled. Bots are not affected by slow-mode, and as such, no changes need to be made to the REST client's internal ratelimiting. This is strictly a read/write cosmetic property for bots. --- .../Entities/Channels/ITextChannel.cs | 3 +++ .../Entities/Channels/TextChannelProperties.cs | 14 +++++++++++++- src/Discord.Net.Rest/API/Common/Channel.cs | 4 +++- .../API/Rest/ModifyTextChannelParams.cs | 4 +++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 ++ .../Entities/Channels/ChannelHelper.cs | 3 ++- .../Entities/Channels/RestTextChannel.cs | 2 ++ .../Entities/Channels/SocketTextChannel.cs | 2 ++ 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 2aa070b03..e9350601d 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -13,6 +13,9 @@ namespace Discord /// Gets the current topic for this text channel. string Topic { get; } + /// Gets the current slow-mode delay for this channel. 0 if disabled. + int SlowMode { get; } + /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); /// Bulk deletes multiple messages. diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index b7b568133..e3c0557dd 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -1,4 +1,6 @@ -namespace Discord +using System; + +namespace Discord { /// public class TextChannelProperties : GuildChannelProperties @@ -11,5 +13,15 @@ /// Should this channel be flagged as NSFW? /// public Optional IsNsfw { get; set; } + /// + /// What the slow-mode ratelimit for this channel should be set to; 0 will disable slow-mode. + /// + /// + /// This value must fall within [0, 120] + /// + /// Users with will be exempt from slow-mode. + /// + /// Throws ArgummentOutOfRange if the value does not fall within [0, 120] + public Optional SlowMode { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 97c35a57b..57a5ce9ab 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; using System; @@ -33,6 +33,8 @@ namespace Discord.API public Optional LastPinTimestamp { get; set; } [JsonProperty("nsfw")] public Optional Nsfw { get; set; } + [JsonProperty("rate_limit_per_user")] + public Optional SlowMode { get; set; } //VoiceChannel [JsonProperty("bitrate")] diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 9cabc67c1..7cb1405c9 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,5 +10,7 @@ namespace Discord.API.Rest public Optional Topic { get; set; } [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } + [JsonProperty("rate_limit_per_user")] + public Optional SlowMode { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 02529310f..f7e86a8f3 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -356,6 +356,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.AtLeast(args.SlowMode, 0, nameof(args.SlowMode)); + Preconditions.AtMost(args.SlowMode, 120, nameof(args.SlowMode)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 50cb352a7..47f82f612 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -45,7 +45,8 @@ namespace Discord.Rest Position = args.Position, CategoryId = args.CategoryId, Topic = args.Topic, - IsNsfw = args.IsNsfw + IsNsfw = args.IsNsfw, + SlowMode = args.SlowMode, }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 7f57c96ff..8fe17ba2f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -12,6 +12,7 @@ namespace Discord.Rest public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } + public int SlowMode { get; private set; } public ulong? CategoryId { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); @@ -34,6 +35,7 @@ namespace Discord.Rest base.Update(model); CategoryId = model.CategoryId; Topic = model.Topic.Value; + SlowMode = model.SlowMode.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index e41c2ba94..d6982bf6a 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,6 +16,7 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } + public int SlowMode { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; @@ -47,6 +48,7 @@ namespace Discord.WebSocket base.Update(state, model); CategoryId = model.CategoryId; Topic = model.Topic.Value; + SlowMode = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? _nsfw = model.Nsfw.GetValueOrDefault(); } From 232f525b59e801332ec9dca755366944c79afa1d Mon Sep 17 00:00:00 2001 From: Christopher F Date: Tue, 11 Sep 2018 18:28:31 -0400 Subject: [PATCH 12/59] lint: refactor SlowMode -> SlowModeInterval this was the name change i always wanted --- src/Discord.Net.Core/Entities/Channels/ITextChannel.cs | 2 +- .../Entities/Channels/TextChannelProperties.cs | 2 +- src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs | 2 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++-- src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs | 2 +- src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 2 +- .../Entities/Channels/SocketTextChannel.cs | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index e9350601d..89e10e65e 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -14,7 +14,7 @@ namespace Discord string Topic { get; } /// Gets the current slow-mode delay for this channel. 0 if disabled. - int SlowMode { get; } + int SlowModeInterval { get; } /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index e3c0557dd..87adccb85 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -22,6 +22,6 @@ namespace Discord /// Users with will be exempt from slow-mode. /// /// Throws ArgummentOutOfRange if the value does not fall within [0, 120] - public Optional SlowMode { get; set; } + public Optional SlowModeInterval { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 7cb1405c9..94f149fc1 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -11,6 +11,6 @@ namespace Discord.API.Rest [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } [JsonProperty("rate_limit_per_user")] - public Optional SlowMode { get; set; } + public Optional SlowModeInterval { get; set; } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index f7e86a8f3..35fa0e989 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -356,8 +356,8 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Position, 0, nameof(args.Position)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); - Preconditions.AtLeast(args.SlowMode, 0, nameof(args.SlowMode)); - Preconditions.AtMost(args.SlowMode, 120, nameof(args.SlowMode)); + Preconditions.AtLeast(args.SlowModeInterval, 0, nameof(args.SlowModeInterval)); + Preconditions.AtMost(args.SlowModeInterval, 120, nameof(args.SlowModeInterval)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 47f82f612..74ce7870f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -46,7 +46,7 @@ namespace Discord.Rest CategoryId = args.CategoryId, Topic = args.Topic, IsNsfw = args.IsNsfw, - SlowMode = args.SlowMode, + SlowModeInterval = args.SlowModeInterval, }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 8fe17ba2f..12437c969 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -12,7 +12,7 @@ namespace Discord.Rest public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { public string Topic { get; private set; } - public int SlowMode { get; private set; } + public int SlowModeInterval { get; private set; } public ulong? CategoryId { get; private set; } public string Mention => MentionUtils.MentionChannel(Id); @@ -35,7 +35,7 @@ namespace Discord.Rest base.Update(model); CategoryId = model.CategoryId; Topic = model.Topic.Value; - SlowMode = model.SlowMode.Value; + SlowModeInterval = model.SlowMode.Value; _nsfw = model.Nsfw.GetValueOrDefault(); } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 3b2935149..bde36922a 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -156,7 +156,7 @@ namespace Discord.Rest { CategoryId = props.CategoryId, Topic = props.Topic, - IsNsfw = props.IsNsfw + IsNsfw = props.IsNsfw, }; var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index d6982bf6a..2e9cd90be 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -16,7 +16,7 @@ namespace Discord.WebSocket private readonly MessageCache _messages; public string Topic { get; private set; } - public int SlowMode { get; private set; } + public int SlowModeInterval { get; private set; } public ulong? CategoryId { get; private set; } public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; @@ -48,7 +48,7 @@ namespace Discord.WebSocket base.Update(state, model); CategoryId = model.CategoryId; Topic = model.Topic.Value; - SlowMode = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? + SlowModeInterval = model.SlowMode.GetValueOrDefault(); // some guilds haven't been patched to include this yet? _nsfw = model.Nsfw.GetValueOrDefault(); } From 674a0fc5dd5181e05e32125f63117b2a1a197344 Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Fri, 28 Sep 2018 15:19:00 -0700 Subject: [PATCH 13/59] Add Ubuntu target to AppVeyor CI build (#1149) * Target Ubuntu in Appveyor CI build AppVeyor has released support for targetting Ubuntu 18.04 environments. This adds Ubuntu to the list of build images for Appveyor to use. * Set clone folder to use appveyor defaults * Check if appveyor provides appveyor-retry by trying to run it * Minor syntax change * Minor syntax change * Skip using appveyor-retry (for now) * Use forward slash for paths so that it works on both windows and linux * Implement replacement for appveyor-retry * Script cleanup * Remove commented out line * Only package releases on Windows * Add the isWindows condition to the deploy stage --- appveyor.yml | 65 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 54b9a1251..0c0c7b102 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,13 +2,17 @@ version: build-{build} branches: only: - dev -image: Visual Studio 2017 +image: +- Visual Studio 2017 +- Ubuntu nuget: disable_publish_on_pr: true pull_requests: do_not_increment_build_number: true -clone_folder: C:\Projects\Discord.Net +# Use the default clone_folder +# Windows: C:\Projects\discord-net +# Ubuntu: /home/appveyor/projects/discord-net cache: test/Discord.Net.Tests/cache.db environment: @@ -20,23 +24,50 @@ init: - ps: $Env:BUILD = "$($Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0"))" build_script: -- ps: appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" +- ps: >- + if ($isLinux) + { + # AppVeyor Linux images do not have appveyor-retry, which retries the commands a few times + # until the command exits with code 0. + # So, this is done with a short script. + $code = 0 + $counter = 0 + do + { + dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" + $code = $LASTEXITCODE + $counter++ + if ($code -ne 0) + { + # Wait 5s before attempting to run again + Start-sleep -Seconds 5 + } + } + until ($counter -eq 5 -or $code -eq 0) + } + else + { + appveyor-retry dotnet restore Discord.Net.sln -v Minimal /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" + } - ps: dotnet build Discord.Net.sln -c "Release" /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" after_build: -- ps: dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" -- ps: dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } +- ps: if ($isWindows) { dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG" } - ps: >- - if ($Env:APPVEYOR_REPO_TAG -eq "true") { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" - } else { - nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" + if ($isWindows) + { + if ($Env:APPVEYOR_REPO_TAG -eq "true") { + nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" + } else { + nuget pack src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" + } } -- ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } +- ps: if ($isWindows) { Get-ChildItem artifacts/*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } } test_script: - ps: >- @@ -52,10 +83,12 @@ deploy: symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package on: branch: dev + isWindows: True - provider: NuGet server: https://www.myget.org/F/rogueexception/api/v2/package api_key: secure: D+vW2O2LBf/iJb4f+q8fkyIW2VdIYIGxSYLWNrOD4BHlDBZQlJipDbNarWjUr2Kn symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package on: - branch: dev \ No newline at end of file + branch: dev + isWindows: True From efaedac98f306e1d855f6dc6f9efb5dd99eeb554 Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Sat, 29 Sep 2018 06:20:38 +0800 Subject: [PATCH 14/59] docs: Fix for incomplete structure sample (#1145) --- docs/guides/getting_started/samples/intro/structure.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index a9a018c3a..5165e2fdb 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -36,7 +36,7 @@ class Program // you must set the MessageCacheSize. You may adjust the number as needed. //MessageCacheSize = 50, - // If your platform doesn't have native websockets, + // If your platform doesn't have native WebSockets, // add Discord.Net.Providers.WS4Net from NuGet, // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance @@ -57,7 +57,7 @@ class Program _commands.Log += Log; // Setup your DI container. - _services = ConfigureServices(), + _services = ConfigureServices(); } @@ -116,7 +116,9 @@ class Program await InitCommands(); // Login and connect. - await _client.LoginAsync(TokenType.Bot, /* */); + await _client.LoginAsync(TokenType.Bot, + // < DO NOT HARDCODE YOUR TOKEN > + Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); // Wait infinitely so your bot actually stays connected. @@ -160,7 +162,7 @@ class Program // Execute the command. (result does not indicate a return value, // rather an object stating if the command executed successfully). - var result = await _commands.ExecuteAsync(context, pos); + var result = await _commands.ExecuteAsync(context, pos, _services); // Uncomment the following lines if you want the bot // to send a message if it failed. From c898325e4594c7b0ea6455c715903c775ec42a2a Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 28 Sep 2018 19:22:48 -0300 Subject: [PATCH 15/59] Fix GetReactionUsersAsync (#1151) --- src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 3dc3e74e9..30ee75c47 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -71,7 +71,7 @@ namespace Discord.Rest }, nextPage: (info, lastPage) => { - if (lastPage.Count != DiscordConfig.MaxUsersPerBatch) + if (lastPage.Count != DiscordConfig.MaxUserReactionsPerBatch) return false; info.Position = lastPage.Max(x => x.Id); From de4d4150533327d29bfb1200835fff6fcff8f50a Mon Sep 17 00:00:00 2001 From: Chris Johnston Date: Fri, 28 Sep 2018 18:46:05 -0700 Subject: [PATCH 16/59] Fix deploy conditions in CI script (#1157) * Fix deploy conditions in CI script The deploy conditions had been using a powershell-only variable, and not a environment variable. This meant that the deployment failed on both Windows and Linux (expected only on Linux). * Remove an extra newline * Remove Debug statements from appveyor build script --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 0c0c7b102..ef5f1667e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -83,7 +83,7 @@ deploy: symbol_server: https://www.myget.org/F/discord-net/symbols/api/v2/package on: branch: dev - isWindows: True + CI_WINDOWS: true - provider: NuGet server: https://www.myget.org/F/rogueexception/api/v2/package api_key: @@ -91,4 +91,4 @@ deploy: symbol_server: https://www.myget.org/F/rogueexception/symbols/api/v2/package on: branch: dev - isWindows: True + CI_WINDOWS: true From c9ba79ec80c055b0ccb05e3f9260aa6a0963bdbd Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 1 Oct 2018 05:09:32 +0800 Subject: [PATCH 17/59] Initial fix (#1160) --- src/Discord.Net.Rest/API/Common/InviteVanity.cs | 10 ++++++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 4 ++-- src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs | 6 ++++-- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Common/InviteVanity.cs diff --git a/src/Discord.Net.Rest/API/Common/InviteVanity.cs b/src/Discord.Net.Rest/API/Common/InviteVanity.cs new file mode 100644 index 000000000..d39792674 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InviteVanity.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + public class InviteVanity + { + [JsonProperty("code")] + public string Code { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 35fa0e989..249e8e946 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -929,13 +929,13 @@ namespace Discord.API } catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) + public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); } public async Task> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index bde36922a..2b7900154 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -213,8 +213,10 @@ namespace Discord.Rest public static async Task GetVanityInviteAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { - var model = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); - return RestInviteMetadata.Create(client, guild, null, model); + var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); + if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); + var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); + return RestInviteMetadata.Create(client, guild, null, inviteModel); } //Roles From 6b21b11f7d2fe4ffb93ae6c394d78bc7c7dcb75b Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sun, 30 Sep 2018 17:10:10 -0400 Subject: [PATCH 18/59] feature: adjust the ratelimit for reactions (#1108) * feature: adjust the ratelimit for reactions allows users to add reactions quickly * fix: don't force DEBUG_LIMITS * fix: undefined behavior using DateTime on intel architerctures it's fine * fix: ensure add/del rxn ends up in the same bucket --- src/Discord.Net.Core/RequestOptions.cs | 3 ++- src/Discord.Net.Rest/DiscordRestApiClient.cs | 10 ++++++++-- src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 5f3a8814b..40f0ebaa4 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; namespace Discord { @@ -22,6 +22,7 @@ namespace Discord internal bool IgnoreState { get; set; } internal string BucketId { get; set; } internal bool IsClientBucket { get; set; } + internal bool IsReactionBucket { get; set; } internal static RequestOptions CreateOrClone(RequestOptions options) { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 249e8e946..1679579b2 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -580,6 +580,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -587,10 +588,13 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/@me", ids, options: options).ConfigureAwait(false); + // @me is non-const to fool the ratelimiter, otherwise it will put add/remove in separate buckets + var me = "@me"; + await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{me}", ids, options: options).ConfigureAwait(false); } public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) { @@ -599,10 +603,12 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); options = RequestOptions.CreateOrClone(options); + options.IsReactionBucket = true; var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{userId}", ids, options: options).ConfigureAwait(false); + var user = CurrentUserId.HasValue ? (userId == CurrentUserId.Value ? "@me" : userId.ToString()) : userId.ToString(); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{user}", ids, options: options).ConfigureAwait(false); } public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index c4f5996c5..d7f9779a4 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -250,6 +250,10 @@ namespace Discord.Net.Queue else if (info.Reset.HasValue) { resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); + + if (request.Options.IsReactionBucket) + resetTick = DateTimeOffset.Now.AddMilliseconds(250); + int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; #if DEBUG_LIMITS Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {info.Lag?.TotalMilliseconds} ms lag)"); From ff0fea98a65d907fbce07856f1a9ef4aebb9108b Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Mon, 1 Oct 2018 05:44:33 +0800 Subject: [PATCH 19/59] Documentation Overhaul (#1161) * Add XML docs * Clean up style switcher * Squash commits on branch docs/faq-n-patches * Fix broken theme selector * Add local image embed instruction * Add a bunch of XML docs * Add a bunch of XML docs * Fix broken search + DocFX by default ships with an older version of jQuery, switching to a newer version confuses parts of the DocFX Javascript. * Minor fixes for CONTRIBUTING.md and README.md * Clean up filterConfig.yml + New config exposes Discord.Net namespace since it has several common public exceptions that may be helpful to users * Add XML docs * Read token from Environment Variable instead of hardcode * Add XMLDocs * Compress some assets & add OAuth2 URL generator * Fix sample link & add missing pictures * Add tag examples * Fix embed docs consistency * Add details regarding userbot support * Add XML Docs * Add XML Docs * Add XML Docs * Minor fixes in documentations + Fix unescaped '<' + Fix typo * Fix seealso for preconditions and add missing descriptions * Add missing exceptions * Document exposed TypeReaders * Fix letter-casing for files * Add 'last modified' plugin Source: https://github.com/Still34/DocFx.Plugin.LastModified Licensed under MIT License * XML Docs * Fix minor consistencies & redundant impl * Add properties examples to overwrite * Fix missing Username prop * Add warning for bulk-delete endpoint * Replace note block * Add BaseSocketClient docs * Add XML docs * Replace langword null to code block null instead - Because DocFX sucks at rendering langword * Replace all langword placements with code block * Add more IGuild docs * Add details to SpotifyGame * Initial proofread of the articles * Add explanation for RunMode * Add event docs - MessageReceived - ChannelUpdated/Destroyed/Created * Fix light theme link color * Fix xml docs error * Add partial documentation for audit log impl * Add documentation for some REST-based objects * Add partial documentation for audit log objects * Add more XML comments to quotation mark alias map stuff, including an example * Add reference to CommandServiceConfig from the util docs' * Add explanation that if " is removed then it wont work * Fix missing service provider in example * Add documentation for new INestedChannel * Add documentation * Add documentation for new API version & few events * Revise guide paragraphs/samples + Fix various formatting. + Provide a more detailed walkthrough for dependency injection. + Add C# note at intro. * Fix typos & formatting * Improve group module example * Small amount to see if I'm doing it right * Remove/cleanup redundant variables * Fix EnterTypingState impl for doc inheritance * Fix Test to resolve changes made in 15b58e * Improve precondition documentation + Add precondition usage sample + Add precondition group usage sample + Move precondition samples to its own sample folder * Move samples to individual folders * Clarify token source * Cleanup styling of README.md for docs * Replace InvalidPathChars for NS1.3 * InvalidPathChars does not exist in NS1.3; replaced with GetInvalidPathChars instead. * Add a missing change for 2c7cc738 * Update LastModified to v1.1.0 & add license * Rewrite installation page for Core 2.1 * Fix anchor link * Bump post-processor to v1.1.1 * Add fixes to partial file & add license * Moved theme-switcher code to scripts partial file + Add author's MIT license to featherlight javascript * Remove unused bootstrap plugin * Bump LastModified plugin * Changed the path from 'lastmodified' to 'last-modified' for consistency * Cleanup README & Contribution guide * Changes to last pr * Fix GetCategoryAsync docs * Proofread and cleanup articles * Change passive voice in "Get Started" to active * Fix improper preposition in Commands Introduction page * Fix minor grammar mistakes in "Your First Bot" (future tense -> present tense/subjunctive mood -> indicative mood/proper noun casing/incorrect noun/add missing article) * Fix minor grammar mistakes in "Installation" (missing article) * no hablo ingles * Try try try again * I'm sure you're having as much fun as I am * Cleanup TOC & fix titles * Improve styling + Change title font to Noto Sans + Add materialized design for commit message box * Add DescriptionGenerator plugin * Add nightly section for clarification * Fix typos in Nightlies & Post-execution * Bump DescriptionGenerator to v1.1.0 + This build adds the functionality of generating managed references' summary into the description tag. * Initial emoji article draft * Add 'additional information' section for emoji article * Add cosmetic changes to the master css * Alter info box color + Add transition to article content * Add clarification in the emoji article * Emphasize that normal emoji string will not translate to its Unicode representation. * Clean up or add some of the samples featured in the article. + Add emoji/emote declaration section for clarification. + Add WebSocket emote sample. - Remove inconsistent styling ('wacky memes' proves to be too out of place). * Improve readability for nightlies article * Move 'Bundled Preconditions' section * Bump LastModified to fix UTC DateTime parsing * Add langwordMapping.yml * Add XML docs * Add VSC workspace rule * The root workspace limits the ruler to 120 characters for member documentations and excludes folders such as 'samples' and 'docs'. * The docs workspace limits the ruler to 70 characters for standard conceptual article to comply with documentation's CONTRIBUTING.md rule, and excludes temprorary folders created by DocFX. * Update CONTRIBUTING.md * Add documentation style rule * Fix styling of several member documentation * Fix ' />' caused by Agent Smith oddities * Fix styling to be more specific about the mention of IDs * Fix exception summary to comply with official Microsoft Docs style * References https://docs.microsoft.com/en-us/dotnet/api/system.argumentnullexception?view=netframework-4.7.2 https://docs.microsoft.com/en-us/dotnet/api/system.platformnotsupportedexception?view=netframework-4.7.2 https://docs.microsoft.com/en-us/dotnet/api/system.badimageformatexception?view=netframework-4.7.2 * Add XML documentations * Shift color return docs * Fix minor docs * Added documentation for SocketDMChannel, SocketGuildChannel, and SocketTextChannel * Add XML docs * Corrections to SocketGuildChannel * Corrections to SocketTextChannel * Corrections to SocketDMChannel * Swapped out 'id' for 'snowflake identifier * Swapped out 'id' for 'snowflake identifier' * SocketDMChannel amendments * SocketGuildChannel amendments * SocketTextChannel amendments * Add XML docs & patch return types + Starting from this commit, all return types for tasks will use style similar to most documentations featured on docs.microsoft.com References: https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.-ctor?view=efcore-2.1 https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream.readasync?view=netcore-2.1 https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writelineasync?view=netcore-2.1#System_IO_TextWriter_WriteLineAsync_System_Char___ And many more other asynchronous method documentations featured in the latest BCL. * Added documentation for many audit log data types, fixed vowel indefinite articles * Change audit log data types to start with 'Contains' (verb) instead of an article * Fix some documentation issues and document some more audit log data types * Fix English posession * Add XML doc * Documented two more types * Documented RoleCreateAuditLogData * Document remaining audit log data types * Added RestDMChannel documentation * Added RestGuildChannel documentation * Added RestTextChannel documentation * Added RestVoiceChannel documentation * Added RestUser documentation * Added RestRole documentation * Added RestMessage documentation * Slightly better wording * Contains -> Contains a piece of (describe article) * [EN] Present perf. -> past perf. * Add XML docs * Fix arrow alignment * Clarify supported nullable type * Fixed a typo in ISnowflakeEntity * Added RestUser Documentation * Added RestInvite documentation * Add XML docs & minor optimizations * Minor optimization for doc rendering * Rollback font optimization changes * Amendments to RestUser * Added SocketDMChannel documentation * Added RestDMChannel documentation * Added RestGuild documentation * Adjustment to SocketDMChannel * Added minimal descriptions from the API documentation for Integration types * Added obsolete mention to the ReadMessages flag. * Added remarks about 2FA requirement for guild permissions * Added xmldoc for GuildPermission methods * Added xml doc for ToAllowList and ToDenyList * Added specification of how the bits of the color raw value are packed * Added discord API documentation to IConnection interface * I can spell :^) * Fix whitespace in ChannelPermission * fix spacing of values in guildpermission * Made changes to get field descriptions from feedback, added returns tag to IConnection * Added property get standard for IntegrationAccount * Added property get pattern to xml docs and identical returns tag. * Change all color class references to struct ...because it isn't a class. * Add XML docs * Rewrote the returns tags in IGuildIntegration, removed the ones I was unsure about. * Rewrote the rest of the returns tags * Amendments * Cleanup doc for c1d78189 * Added types to tags where missing * Added second sample for adding reactions * Added some class summaries * Missed a period * Amendments * restored the removed line break * Removed unnecessary see tag * Use consistent quotation marks around subscribers, the name for these users are dependant on the source of where they are integrated from (youtube or twitch), so we should not use a name that is specific to one platform * Add tag to the IGuildIntegration xmldocs * Fix grammar issue * Update DescriptionGenerator * Cleanup of https://github.com/Still34/Discord.Net/pull/8 * Cleanup previous PR * Fix for misleading behaviour in the emoji guide + Original lines stated that sending a emoji wrapped in colon will not be parsed, but that was incorrect; replaced with reactions instead of sending messages as the example * Add strings for dictionary in DotSettings * Add XML docs * Fix lots of typos in comments + Geez, I didn't know there were so many. * Add XML docs & rewrite GetMessagesAsync docs This commit rewrites the remarks section of GetMessagesAsync, as well as adding examples to several methods. * Update 'Your First Bot' + This commit reflects the new changes made to the Discord Application Developer Portal after its major update * Initial optimization for DocFX render & add missing files * Add examples in message methods * Cleanup https://github.com/RogueException/Discord.Net/pull/1128 * Fix first bot note * Cleanup FAQ structure * Add XML docs * Update docfx plugins * Fix navbar collapsing issue * Fix broken xref * Cleanup FAQ section + Add introductory paragraphs to each FAQ section. + Add 'missing dependency' entry to commands FAQ. * Split commands FAQ to 'General' and 'DI' sections. * Cleanup https://github.com/RogueException/Discord.Net/pull/1139 * Fix missing namespace * Add missing highlighting css for the light theme * Add additional clarification for installing packages * Add indentation to example for clarity * Cleanup several articles to be more human-friendly and easier to read * Remove RPC-related notes * Cleanup slow-mode-related documentation strings * Add an additional note about cross-guild emote usage * Add CreateTextChannel sample * Add XMLDocs --- .gitignore | 3 + CONTRIBUTING.md | 20 +- Discord.Net.code-workspace | 23 + Discord.Net.sln.DotSettings | 16 + docs/CONTRIBUTING.md | 39 +- docs/Discord.Net.Docs.code-workspace | 21 + docs/README.md | 19 +- .../Commands/CommandException.Overwrite.md | 31 + .../DontAutoLoadAttribute.Overwrite.md | 22 + .../Commands/DontInjectAttribute.Overwrite.md | 27 + .../Commands/ICommandContext.Inclusion.md | 5 + .../Commands/ICommandContext.Overwrite.md | 27 + .../PreconditionAttribute.Overwrites.md | 103 ++ ...PreconditionAttribute.Remarks.Inclusion.md | 6 + .../Common/EmbedBuilder.Overwrites.md | 68 ++ .../Common/EmbedObjectBuilder.Inclusion.md | 25 + .../Common/EmbedObjectBuilder.Overwrites.md | 20 + docs/_overwrites/Common/IEmote.Inclusion.md | 26 + docs/_overwrites/Common/IEmote.Overwrites.md | 81 ++ .../Common/ObjectProperties.Overwrites.md | 174 +++ .../OverrideTypeReaderAttribute.Overwrites.md | 24 + .../Common/images/embed-example.png | Bin 0 -> 15290 bytes .../Common/images/react-example.png | Bin 0 -> 8994 bytes .../DocFX.Plugin.DescriptionGenerator.dll | Bin 0 -> 9216 bytes .../description-generator/plugins/LICENSE | 21 + docs/_template/last-modified/plugins/LICENSE | 21 + .../plugins/LastModifiedPostProcessor.dll | Bin 0 -> 11776 bytes .../partials/affix.tmpl.partial | 33 + .../partials/head.tmpl.partial | 27 + .../partials/scripts.tmpl.partial | 10 + .../light-dark-theme/styles/cornerify.js | 3 + .../light-dark-theme/styles/dark.css | 304 +++++ .../styles/docfx.vendor.minify.css | 1022 +++++++++++++++++ .../light-dark-theme/styles/gray.css | 311 +++++ .../light-dark-theme/styles/light.css | 113 ++ .../light-dark-theme/styles/master.css | 156 +++ .../styles/plugin-featherlight.js | 37 + .../light-dark-theme/styles/styleswitcher.js | 26 + .../styles/theme-switcher.css | 9 + .../light-dark-theme/styles/tomorrow.css | 72 ++ .../light-dark-theme/styles/vs2015.css | 115 ++ docs/api/index.md | 19 +- docs/docfx.json | 96 +- docs/faq/basics/basic-operations.md | 123 ++ docs/faq/basics/client-basics.md | 66 ++ docs/faq/basics/getting-started.md | 79 ++ docs/faq/basics/images/dev-mode.png | Bin 0 -> 80742 bytes docs/faq/basics/images/mention-escape.png | Bin 0 -> 19611 bytes docs/faq/basics/images/snowflake.png | Bin 0 -> 73062 bytes docs/faq/basics/samples/cast.cs | 15 + docs/faq/basics/samples/emoji-others.cs | 18 + docs/faq/basics/samples/emoji-self.cs | 17 + docs/faq/commands/dependency-injection.md | 54 + docs/faq/commands/general.md | 142 +++ docs/faq/commands/samples/DI.cs | 28 + docs/faq/commands/samples/Remainder.cs | 20 + docs/faq/commands/samples/missing-dep.cs | 29 + .../faq/commands/samples/runmode-cmdattrib.cs | 7 + .../faq/commands/samples/runmode-cmdconfig.cs | 10 + docs/faq/misc/glossary.md | 82 ++ docs/faq/misc/legacy.md | 29 + docs/faq/toc.yml | 18 + docs/filterConfig.yml | 12 +- docs/guides/commands/commands.md | 343 ------ docs/guides/commands/dependency-injection.md | 47 + docs/guides/commands/intro.md | 221 ++++ docs/guides/commands/post-execution.md | 122 ++ docs/guides/commands/preconditions.md | 83 ++ .../commands/samples/command_handler.cs | 63 - .../dependency_map_setup.cs | 62 + .../dependency-injection/dependency_module.cs | 37 + .../dependency_module_noinject.cs | 29 + .../commands/samples/dependency_map_setup.cs | 18 - .../commands/samples/dependency_module.cs | 40 - docs/guides/commands/samples/empty-module.cs | 6 - .../commands/samples/intro/command_handler.cs | 63 + .../commands/samples/intro/empty-module.cs | 8 + .../commands/samples/{ => intro}/groups.cs | 15 +- .../commands/samples/{ => intro}/module.cs | 26 +- .../post-execution/command_exception_log.cs | 13 + .../command_executed_adv_demo.cs | 13 + .../post-execution/command_executed_demo.cs | 38 + .../post-execution/customresult_base.cs | 6 + .../post-execution/customresult_extended.cs | 10 + .../post-execution/customresult_usage.cs | 10 + .../post-execution/post-execution_basic.cs | 11 + .../preconditions/group_precondition.cs | 9 + .../preconditions/precondition_usage.cs | 3 + .../{ => preconditions}/require_owner.cs | 10 +- .../typereaders/typereader-register.cs | 29 + .../samples/{ => typereaders}/typereader.cs | 5 +- docs/guides/commands/typereaders.md | 70 ++ docs/guides/concepts/connections.md | 43 +- docs/guides/concepts/entities.md | 53 +- docs/guides/concepts/events.md | 36 +- docs/guides/concepts/logging.md | 39 +- docs/guides/concepts/samples/connections.cs | 2 +- docs/guides/concepts/samples/entities.cs | 8 +- docs/guides/concepts/samples/events.cs | 2 +- docs/guides/concepts/samples/logging.cs | 2 +- docs/guides/deployment/deployment.md | 86 ++ docs/guides/emoji/emoji.md | 100 ++ docs/guides/emoji/images/emojipedia.png | Bin 0 -> 15043 bytes docs/guides/emoji/images/emote-format.png | Bin 0 -> 14696 bytes .../emoji/images/fileformat-emoji-src.png | Bin 0 -> 23541 bytes docs/guides/emoji/samples/emoji-sample.cs | 6 + docs/guides/emoji/samples/emote-sample.cs | 7 + .../emoji/samples/socket-emote-sample.cs | 11 + docs/guides/getting_started/first-bot.md | 263 +++++ .../images/appveyor-artifacts.png | Bin 0 -> 7002 bytes .../getting_started/images/appveyor-nupkg.png | Bin 0 -> 9412 bytes .../images/install-vs-nuget.png | Bin 119670 -> 49542 bytes .../getting_started/images/intro-add-bot.png | Bin 26232 -> 6417 bytes .../images/intro-authorize.png | Bin 0 -> 19052 bytes .../images/intro-bot-settings.png | Bin 0 -> 5704 bytes .../images/intro-client-id.png | Bin 5109 -> 0 bytes .../images/intro-create-app.png | Bin 52035 -> 0 bytes .../images/intro-create-bot.png | Bin 45743 -> 0 bytes .../images/intro-oauth-settings.png | Bin 0 -> 5655 bytes .../images/intro-public-bot.png | Bin 0 -> 11135 bytes .../images/intro-scopes-bot.png | Bin 0 -> 17423 bytes .../getting_started/images/intro-token.png | Bin 26594 -> 18552 bytes .../images/nightlies-vs-note.png | Bin 0 -> 10162 bytes .../images/nightlies-vs-step1.png | Bin 0 -> 13372 bytes .../images/nightlies-vs-step2.png | Bin 0 -> 13713 bytes .../images/nightlies-vs-step4.png | Bin 0 -> 8214 bytes docs/guides/getting_started/installing.md | 156 ++- docs/guides/getting_started/intro.md | 237 ---- docs/guides/getting_started/nightlies.md | 86 ++ .../samples/first-bot/async-context.cs | 9 + .../samples/first-bot/client.cs | 21 + .../samples/first-bot/complete.cs | 34 + .../samples/first-bot/logging.cs | 5 + .../samples/{intro => first-bot}/message.cs | 2 +- .../samples/{intro => first-bot}/structure.cs | 0 .../samples/intro/async-context.cs | 15 - .../getting_started/samples/intro/client.cs | 17 - .../getting_started/samples/intro/complete.cs | 44 - .../getting_started/samples/intro/logging.cs | 22 - .../getting_started/samples/project.csproj | 13 - .../getting_started/samples/project.xml | 17 + docs/guides/getting_started/terminology.md | 40 +- docs/guides/introduction/intro.md | 54 + docs/guides/migrating/migrating.md | 61 - docs/guides/migrating/samples/event.cs | 4 - docs/guides/migrating/samples/sync_event.cs | 5 - docs/guides/toc.yml | 42 +- docs/guides/voice/sending-voice.md | 5 +- docs/index.md | 31 +- docs/langwordMapping.yml | 61 + docs/toc.yml | 7 +- .../Discord.Net.Rpc/DiscordRpcApiClient.cs | 2 +- .../Discord.Net.Rpc/DiscordRpcConfig.cs | 4 +- .../Entities/Channels/RpcDMChannel.cs | 1 + .../Entities/Channels/RpcGroupChannel.cs | 1 + samples/01_basic_ping_bot/Program.cs | 6 +- samples/02_commands_framework/Program.cs | 2 +- .../Attributes/AliasAttribute.cs | 29 +- .../Attributes/CommandAttribute.cs | 15 + .../Attributes/DontAutoLoadAttribute.cs | 8 + .../Attributes/DontInjectAttribute.cs | 34 +- .../Attributes/GroupAttribute.cs | 11 + .../Attributes/NameAttribute.cs | 10 + .../Attributes/OverrideTypeReaderAttribute.cs | 31 +- .../ParameterPreconditionAttribute.cs | 11 + .../Attributes/PreconditionAttribute.cs | 21 +- .../RequireBotPermissionAttribute.cs | 44 +- .../Preconditions/RequireContextAttribute.cs | 34 +- .../Preconditions/RequireNsfwAttribute.cs | 24 +- .../Preconditions/RequireOwnerAttribute.cs | 33 +- .../RequireUserPermissionAttribute.cs | 47 +- .../Attributes/PriorityAttribute.cs | 12 +- .../Attributes/RemainderAttribute.cs | 3 + .../Attributes/RemarksAttribute.cs | 3 + .../Attributes/SummaryAttribute.cs | 3 + .../Builders/CommandBuilder.cs | 1 + .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/CommandContext.cs | 16 +- src/Discord.Net.Commands/CommandError.cs | 27 +- src/Discord.Net.Commands/CommandException.cs | 13 + src/Discord.Net.Commands/CommandMatch.cs | 5 +- src/Discord.Net.Commands/CommandParser.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 212 +++- .../CommandServiceConfig.cs | 49 +- .../Extensions/MessageExtensions.cs | 18 + src/Discord.Net.Commands/Info/CommandInfo.cs | 63 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 39 + .../Info/ParameterInfo.cs | 35 +- .../Map/CommandMapNode.cs | 1 + src/Discord.Net.Commands/ModuleBase.cs | 39 +- .../MultiMatchHandling.cs | 7 +- .../Readers/ChannelTypeReader.cs | 14 +- .../Readers/EnumTypeReader.cs | 7 +- .../Readers/MessageTypeReader.cs | 9 +- .../Readers/NullableTypeReader.cs | 5 +- .../Readers/PrimitiveTypeReader.cs | 8 +- .../Readers/RoleTypeReader.cs | 9 +- .../Readers/TimeSpanTypeReader.cs | 1 + .../Readers/TypeReader.cs | 14 +- .../Readers/UserTypeReader.cs | 16 +- .../Results/ExecuteResult.cs | 54 +- src/Discord.Net.Commands/Results/IResult.cs | 24 +- .../Results/ParseResult.cs | 8 +- .../Results/PreconditionGroupResult.cs | 2 +- .../Results/PreconditionResult.cs | 27 + .../Results/RuntimeResult.cs | 12 +- .../Results/SearchResult.cs | 3 + .../Results/TypeReaderResult.cs | 5 + src/Discord.Net.Commands/RunMode.cs | 16 +- .../Utilities/QuotationAliasUtils.cs | 11 +- .../Utilities/ReflectionUtils.cs | 11 +- src/Discord.Net.Core/Audio/AudioOutStream.cs | 17 +- src/Discord.Net.Core/Audio/AudioStream.cs | 34 +- src/Discord.Net.Core/Audio/IAudioClient.cs | 2 +- src/Discord.Net.Core/CDN.cs | 85 ++ .../Commands/ICommandContext.cs | 20 +- src/Discord.Net.Core/ConnectionState.cs | 7 +- src/Discord.Net.Core/DiscordConfig.cs | 118 +- .../Entities/Activities/ActivityType.cs | 17 +- .../Entities/Activities/Game.cs | 11 + .../Entities/Activities/GameAsset.cs | 27 +- .../Entities/Activities/GameParty.cs | 15 + .../Entities/Activities/GameSecrets.cs | 16 +- .../Entities/Activities/GameTimestamps.cs | 13 +- .../Entities/Activities/IActivity.cs | 17 +- .../Entities/Activities/RichGame.cs | 32 +- .../Entities/Activities/SpotifyGame.cs | 63 + .../Entities/Activities/StreamingGame.cs | 18 +- .../Entities/AuditLogs/ActionType.cs | 86 +- .../Entities/AuditLogs/IAuditLogData.cs | 8 +- .../Entities/AuditLogs/IAuditLogEntry.cs | 22 +- src/Discord.Net.Core/Entities/CacheMode.cs | 11 +- .../Entities/Channels/ChannelType.cs | 8 +- .../Entities/Channels/Direction.cs | 14 +- .../Channels/GuildChannelProperties.cs | 27 +- .../Entities/Channels/IAudioChannel.cs | 22 +- .../Entities/Channels/ICategoryChannel.cs | 9 +- .../Entities/Channels/IChannel.cs | 38 +- .../Entities/Channels/IDMChannel.cs | 22 +- .../Entities/Channels/IGroupChannel.cs | 15 +- .../Entities/Channels/IGuildChannel.cs | 163 ++- .../Entities/Channels/IMessageChannel.cs | 262 ++++- .../Entities/Channels/INestedChannel.cs | 21 +- .../Entities/Channels/IPrivateChannel.cs | 11 +- .../Entities/Channels/ITextChannel.cs | 104 +- .../Entities/Channels/IVoiceChannel.cs | 29 +- .../Channels/ReorderChannelProperties.cs | 22 +- .../Channels/TextChannelProperties.cs | 22 +- .../Channels/VoiceChannelProperties.cs | 10 +- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 24 +- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 49 +- .../Entities/Emotes/EmoteProperties.cs | 12 +- .../Entities/Emotes/GuildEmote.cs | 28 +- .../Entities/Emotes/IEmote.cs | 9 +- .../Guilds/DefaultMessageNotifications.cs | 13 +- .../Entities/Guilds/GuildEmbedProperties.cs | 10 +- .../Guilds/GuildIntegrationProperties.cs | 14 +- .../Entities/Guilds/GuildProperties.cs | 47 +- src/Discord.Net.Core/Entities/Guilds/IBan.cs | 17 +- .../Entities/Guilds/IGuild.cs | 629 +++++++++- .../Entities/Guilds/IGuildIntegration.cs | 54 +- .../Entities/Guilds/IUserGuild.cs | 18 +- .../Entities/Guilds/IVoiceRegion.cs | 45 +- .../Entities/Guilds/IntegrationAccount.cs | 6 +- .../Entities/Guilds/MfaLevel.cs | 13 +- .../Entities/Guilds/PermissionTarget.cs | 11 +- .../Entities/Guilds/VerificationLevel.cs | 25 +- src/Discord.Net.Core/Entities/IApplication.cs | 20 +- src/Discord.Net.Core/Entities/IDeletable.cs | 10 +- src/Discord.Net.Core/Entities/IEntity.cs | 4 +- src/Discord.Net.Core/Entities/IMentionable.cs | 12 +- .../Entities/ISnowflakeEntity.cs | 7 + src/Discord.Net.Core/Entities/IUpdateable.cs | 9 +- src/Discord.Net.Core/Entities/Image.cs | 37 +- src/Discord.Net.Core/Entities/ImageFormat.cs | 20 +- .../Entities/Invites/IInvite.cs | 75 +- .../Entities/Invites/IInviteMetadata.cs | 55 +- .../Entities/Messages/Embed.cs | 22 + .../Entities/Messages/EmbedAuthor.cs | 22 +- .../Entities/Messages/EmbedBuilder.cs | 396 ++++++- .../Entities/Messages/EmbedField.cs | 20 +- .../Entities/Messages/EmbedFooter.cs | 32 +- .../Entities/Messages/EmbedImage.cs | 34 +- .../Entities/Messages/EmbedProvider.cs | 20 +- .../Entities/Messages/EmbedThumbnail.cs | 34 +- .../Entities/Messages/EmbedType.cs | 30 + .../Entities/Messages/EmbedVideo.cs | 30 +- .../Entities/Messages/IAttachment.cs | 47 +- .../Entities/Messages/IEmbed.cs | 84 +- .../Entities/Messages/IMessage.cs | 95 +- .../Entities/Messages/IReaction.cs | 8 +- .../Entities/Messages/ISystemMessage.cs | 5 +- .../Entities/Messages/IUserMessage.cs | 104 +- .../Entities/Messages/MessageProperties.cs | 27 +- .../Entities/Messages/MessageSource.cs | 15 + .../Entities/Messages/MessageType.cs | 26 +- .../Entities/Messages/ReactionMetadata.cs | 19 +- .../Entities/Messages/TagHandling.cs | 42 +- .../Entities/Messages/TagType.cs | 9 +- .../Entities/Permissions/ChannelPermission.cs | 99 +- .../Permissions/ChannelPermissions.cs | 70 +- .../Entities/Permissions/GuildPermission.cs | 135 ++- .../Entities/Permissions/GuildPermissions.cs | 77 +- .../Entities/Permissions/Overwrite.cs | 21 +- .../Permissions/OverwritePermissions.cs | 45 +- .../Entities/Permissions/PermValue.cs | 6 +- src/Discord.Net.Core/Entities/Roles/Color.cs | 137 ++- src/Discord.Net.Core/Entities/Roles/IRole.cs | 80 +- .../Entities/Roles/ReorderRoleProperties.cs | 24 +- .../Entities/Roles/RoleProperties.cs | 37 +- .../Entities/Users/GuildUserProperties.cs | 59 +- .../Entities/Users/IConnection.cs | 15 +- .../Entities/Users/IGroupUser.cs | 5 +- .../Entities/Users/IGuildUser.cs | 115 +- .../Entities/Users/IPresence.cs | 15 +- .../Entities/Users/ISelfUser.cs | 26 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 54 +- .../Entities/Users/IVoiceState.cs | 33 +- .../Entities/Users/IWebhookUser.cs | 4 +- .../Entities/Users/SelfUserProperties.cs | 18 +- .../Entities/Users/UserStatus.cs | 23 +- .../Entities/Webhooks/IWebhook.cs | 43 +- .../Entities/Webhooks/WebhookProperties.cs | 27 +- .../Extensions/AsyncEnumerableExtensions.cs | 10 +- .../Extensions/DiscordClientExtensions.cs | 9 +- .../Extensions/EmbedBuilderExtensions.cs | 11 + .../Extensions/MessageExtensions.cs | 10 + .../Extensions/UserExtensions.cs | 119 +- src/Discord.Net.Core/Format.cs | 1 + src/Discord.Net.Core/IDiscordClient.cs | 238 +++- src/Discord.Net.Core/Logging/LogManager.cs | 10 +- src/Discord.Net.Core/Logging/LogMessage.cs | 41 +- src/Discord.Net.Core/Logging/LogSeverity.cs | 24 +- src/Discord.Net.Core/LoginState.cs | 7 +- src/Discord.Net.Core/Net/HttpException.cs | 32 + src/Discord.Net.Core/Net/IRequest.cs | 3 + .../Net/RateLimitedException.cs | 10 + src/Discord.Net.Core/Net/Rest/IRestClient.cs | 21 + .../Net/WebSocketClosedException.cs | 20 +- src/Discord.Net.Core/RequestOptions.cs | 40 +- src/Discord.Net.Core/RetryMode.cs | 4 +- src/Discord.Net.Core/TokenType.cs | 10 + src/Discord.Net.Core/Utils/Cacheable.cs | 37 +- src/Discord.Net.Core/Utils/Comparers.cs | 18 + .../Utils/ConcurrentHashSet.cs | 8 +- src/Discord.Net.Core/Utils/DateTimeUtils.cs | 3 +- src/Discord.Net.Core/Utils/MentionUtils.cs | 56 +- src/Discord.Net.Core/Utils/Optional.cs | 3 +- src/Discord.Net.Core/Utils/Permissions.cs | 2 +- src/Discord.Net.Core/Utils/Preconditions.cs | 2 + src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 17 + src/Discord.Net.Core/Utils/TokenUtils.cs | 11 +- .../WS4NetClient.cs | 4 +- .../API/Common/EmbedAuthor.cs | 1 - .../API/Common/EmbedFooter.cs | 1 - src/Discord.Net.Rest/API/Common/EmbedImage.cs | 3 +- .../API/Common/EmbedProvider.cs | 3 +- .../API/Common/EmbedThumbnail.cs | 3 +- src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 3 +- .../API/Rest/UploadFileParams.cs | 1 - src/Discord.Net.Rest/BaseDiscordClient.cs | 35 +- src/Discord.Net.Rest/ClientHelper.cs | 4 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 47 +- src/Discord.Net.Rest/DiscordRestClient.cs | 54 +- src/Discord.Net.Rest/DiscordRestConfig.cs | 5 +- .../AuditLogs/DataTypes/BanAuditLogData.cs | 11 +- .../DataTypes/ChannelCreateAuditLogData.cs | 29 +- .../DataTypes/ChannelDeleteAuditLogData.cs | 30 +- .../AuditLogs/DataTypes/ChannelInfo.cs | 29 + .../DataTypes/ChannelUpdateAuditLogData.cs | 23 +- .../DataTypes/EmoteCreateAuditLogData.cs | 19 +- .../DataTypes/EmoteDeleteAuditLogData.cs | 17 +- .../DataTypes/EmoteUpdateAuditLogData.cs | 23 +- .../Entities/AuditLogs/DataTypes/GuildInfo.cs | 55 + .../DataTypes/GuildUpdateAuditLogData.cs | 17 +- .../DataTypes/InviteCreateAuditLogData.cs | 49 +- .../DataTypes/InviteDeleteAuditLogData.cs | 49 +- .../AuditLogs/DataTypes/InviteInfo.cs | 37 + .../DataTypes/InviteUpdateAuditLogData.cs | 15 + .../AuditLogs/DataTypes/KickAuditLogData.cs | 9 + .../DataTypes/MemberRoleAuditLogData.cs | 17 +- .../AuditLogs/DataTypes/MemberRoleEditInfo.cs | 21 + .../DataTypes/MemberUpdateAuditLogData.cs | 12 +- .../DataTypes/MessageDeleteAuditLogData.cs | 16 + .../DataTypes/OverwriteCreateAuditLogData.cs | 9 + .../DataTypes/OverwriteDeleteAuditLogData.cs | 15 +- .../DataTypes/OverwriteUpdateAuditLogData.cs | 30 +- .../AuditLogs/DataTypes/PruneAuditLogData.cs | 18 + .../DataTypes/RoleCreateAuditLogData.cs | 15 + .../DataTypes/RoleDeleteAuditLogData.cs | 15 + .../AuditLogs/DataTypes/RoleEditInfo.cs | 38 + .../DataTypes/RoleUpdateAuditLogData.cs | 21 + .../AuditLogs/DataTypes/UnbanAuditLogData.cs | 9 + .../DataTypes/WebhookCreateAuditLogData.cs | 35 +- .../DataTypes/WebhookDeleteAuditLogData.cs | 38 +- .../AuditLogs/DataTypes/WebhookInfo.cs | 22 + .../DataTypes/WebhookUpdateAuditLogData.cs | 28 +- .../Entities/AuditLogs/RestAuditLogEntry.cs | 3 + .../Entities/Channels/ChannelHelper.cs | 41 +- .../Entities/Channels/IRestMessageChannel.cs | 186 ++- .../Entities/Channels/IRestPrivateChannel.cs | 8 +- .../Entities/Channels/RestCategoryChannel.cs | 13 +- .../Entities/Channels/RestChannel.cs | 10 + .../Entities/Channels/RestDMChannel.cs | 94 +- .../Entities/Channels/RestGroupChannel.cs | 50 +- .../Entities/Channels/RestGuildChannel.cs | 113 +- .../Entities/Channels/RestTextChannel.cs | 133 ++- .../Entities/Channels/RestVoiceChannel.cs | 20 + .../Channels/RpcVirtualMessageChannel.cs | 27 +- .../Entities/Guilds/GuildHelper.cs | 24 +- .../Entities/Guilds/RestBan.cs | 19 +- .../Entities/Guilds/RestGuild.cs | 387 ++++++- .../Entities/Guilds/RestGuildEmbed.cs | 4 +- .../Entities/Guilds/RestGuildIntegration.cs | 14 +- .../Entities/Guilds/RestUserGuild.cs | 8 +- .../Entities/Guilds/RestVoiceRegion.cs | 3 + .../Entities/Invites/RestInvite.cs | 22 +- .../Entities/Invites/RestInviteMetadata.cs | 11 + .../Entities/Messages/Attachment.cs | 18 +- .../Entities/Messages/MessageHelper.cs | 6 +- .../Entities/Messages/RestMessage.cs | 40 +- .../Entities/Messages/RestReaction.cs | 12 +- .../Entities/Messages/RestSystemMessage.cs | 6 +- .../Entities/Messages/RestUserMessage.cs | 26 +- .../Entities/RestApplication.cs | 21 +- .../Entities/Roles/RestRole.cs | 27 +- .../Entities/Users/RestConnection.cs | 13 +- .../Entities/Users/RestGroupUser.cs | 14 +- .../Entities/Users/RestGuildUser.cs | 23 + .../Entities/Users/RestSelfUser.cs | 13 +- .../Entities/Users/RestUser.cs | 32 +- .../Entities/Users/RestWebhookUser.cs | 54 +- .../Entities/Users/UserHelper.cs | 4 +- .../Entities/Webhooks/RestWebhook.cs | 15 +- .../Net/Converters/ImageConverter.cs | 3 +- .../Converters/PermissionTargetConverter.cs | 8 +- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 4 +- .../Net/DefaultRestClientProvider.cs | 1 + .../Net/Queue/RequestQueue.cs | 2 +- .../Net/Queue/RequestQueueBucket.cs | 2 +- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 16 +- .../Audio/AudioClient.cs | 1 - .../Audio/Streams/OpusDecodeStream.cs | 9 +- .../Audio/Streams/OpusEncodeStream.cs | 2 +- .../Audio/Streams/RTPReadStream.cs | 5 +- .../Audio/Streams/SodiumDecryptStream.cs | 4 +- .../Audio/Streams/SodiumEncryptStream.cs | 14 +- .../BaseSocketClient.Events.cs | 88 ++ src/Discord.Net.WebSocket/BaseSocketClient.cs | 228 +++- src/Discord.Net.WebSocket/ClientState.cs | 2 +- .../Commands/ShardedCommandContext.cs | 6 +- .../Commands/SocketCommandContext.cs | 33 +- .../DiscordShardedClient.cs | 40 +- .../DiscordSocketApiClient.cs | 10 +- .../DiscordSocketClient.Events.cs | 9 +- .../DiscordSocketClient.cs | 114 +- .../DiscordSocketConfig.cs | 55 +- .../Entities/Channels/ISocketAudioChannel.cs | 5 +- .../Channels/ISocketMessageChannel.cs | 111 +- .../Channels/ISocketPrivateChannel.cs | 5 +- .../Channels/SocketCategoryChannel.cs | 24 +- .../Entities/Channels/SocketChannel.cs | 23 +- .../Entities/Channels/SocketChannelHelper.cs | 9 +- .../Entities/Channels/SocketDMChannel.cs | 174 ++- .../Entities/Channels/SocketGroupChannel.cs | 52 +- .../Entities/Channels/SocketGuildChannel.cs | 126 +- .../Entities/Channels/SocketTextChannel.cs | 196 +++- .../Entities/Channels/SocketVoiceChannel.cs | 21 + .../Entities/Guilds/SocketGuild.cs | 368 +++++- .../Entities/Messages/MessageCache.cs | 4 +- .../Entities/Messages/SocketMessage.cs | 72 +- .../Entities/Messages/SocketReaction.cs | 40 +- .../Entities/Messages/SocketSystemMessage.cs | 6 +- .../Entities/Messages/SocketUserMessage.cs | 27 +- .../Entities/Roles/SocketRole.cs | 36 +- .../Entities/SocketEntity.cs | 3 +- .../Entities/Users/SocketGlobalUser.cs | 5 +- .../Entities/Users/SocketGroupUser.cs | 23 +- .../Entities/Users/SocketGuildUser.cs | 57 +- .../Entities/Users/SocketPresence.cs | 14 +- .../Entities/Users/SocketSelfUser.cs | 18 +- .../Entities/Users/SocketUnknownUser.cs | 21 +- .../Entities/Users/SocketUser.cs | 26 +- .../Entities/Users/SocketVoiceState.cs | 27 +- .../Entities/Users/SocketWebhookUser.cs | 70 +- .../Entities/Voice/SocketVoiceServer.cs | 27 +- .../Net/DefaultWebSocketClientProvider.cs | 1 + .../DiscordWebhookClient.cs | 20 +- .../Entities/Webhooks/RestInternalWebhook.cs | 2 +- .../WebhookClientHelper.cs | 5 +- .../AnalyzerTests/GuildAccessTests.cs | 6 +- .../Helpers/DiagnosticVerifier.Helper.cs | 3 +- .../Verifiers/DiagnosticVerifier.cs | 5 +- test/Discord.Net.Tests/Net/HttpMixin.cs | 3 +- test/Discord.Net.Tests/Tests.Channels.cs | 13 +- .../Tests.GuildPermissions.cs | 4 +- test/Discord.Net.Tests/Tests.Migrations.cs | 2 +- test/Discord.Net.Tests/Tests.Permissions.cs | 1 - 498 files changed, 16061 insertions(+), 2630 deletions(-) create mode 100644 Discord.Net.code-workspace create mode 100644 Discord.Net.sln.DotSettings create mode 100644 docs/Discord.Net.Docs.code-workspace create mode 100644 docs/_overwrites/Commands/CommandException.Overwrite.md create mode 100644 docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md create mode 100644 docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md create mode 100644 docs/_overwrites/Commands/ICommandContext.Inclusion.md create mode 100644 docs/_overwrites/Commands/ICommandContext.Overwrite.md create mode 100644 docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md create mode 100644 docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md create mode 100644 docs/_overwrites/Common/EmbedBuilder.Overwrites.md create mode 100644 docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md create mode 100644 docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md create mode 100644 docs/_overwrites/Common/IEmote.Inclusion.md create mode 100644 docs/_overwrites/Common/IEmote.Overwrites.md create mode 100644 docs/_overwrites/Common/ObjectProperties.Overwrites.md create mode 100644 docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md create mode 100644 docs/_overwrites/Common/images/embed-example.png create mode 100644 docs/_overwrites/Common/images/react-example.png create mode 100644 docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll create mode 100644 docs/_template/description-generator/plugins/LICENSE create mode 100644 docs/_template/last-modified/plugins/LICENSE create mode 100644 docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll create mode 100644 docs/_template/light-dark-theme/partials/affix.tmpl.partial create mode 100644 docs/_template/light-dark-theme/partials/head.tmpl.partial create mode 100644 docs/_template/light-dark-theme/partials/scripts.tmpl.partial create mode 100644 docs/_template/light-dark-theme/styles/cornerify.js create mode 100644 docs/_template/light-dark-theme/styles/dark.css create mode 100644 docs/_template/light-dark-theme/styles/docfx.vendor.minify.css create mode 100644 docs/_template/light-dark-theme/styles/gray.css create mode 100644 docs/_template/light-dark-theme/styles/light.css create mode 100644 docs/_template/light-dark-theme/styles/master.css create mode 100644 docs/_template/light-dark-theme/styles/plugin-featherlight.js create mode 100644 docs/_template/light-dark-theme/styles/styleswitcher.js create mode 100644 docs/_template/light-dark-theme/styles/theme-switcher.css create mode 100644 docs/_template/light-dark-theme/styles/tomorrow.css create mode 100644 docs/_template/light-dark-theme/styles/vs2015.css create mode 100644 docs/faq/basics/basic-operations.md create mode 100644 docs/faq/basics/client-basics.md create mode 100644 docs/faq/basics/getting-started.md create mode 100644 docs/faq/basics/images/dev-mode.png create mode 100644 docs/faq/basics/images/mention-escape.png create mode 100644 docs/faq/basics/images/snowflake.png create mode 100644 docs/faq/basics/samples/cast.cs create mode 100644 docs/faq/basics/samples/emoji-others.cs create mode 100644 docs/faq/basics/samples/emoji-self.cs create mode 100644 docs/faq/commands/dependency-injection.md create mode 100644 docs/faq/commands/general.md create mode 100644 docs/faq/commands/samples/DI.cs create mode 100644 docs/faq/commands/samples/Remainder.cs create mode 100644 docs/faq/commands/samples/missing-dep.cs create mode 100644 docs/faq/commands/samples/runmode-cmdattrib.cs create mode 100644 docs/faq/commands/samples/runmode-cmdconfig.cs create mode 100644 docs/faq/misc/glossary.md create mode 100644 docs/faq/misc/legacy.md create mode 100644 docs/faq/toc.yml delete mode 100644 docs/guides/commands/commands.md create mode 100644 docs/guides/commands/dependency-injection.md create mode 100644 docs/guides/commands/intro.md create mode 100644 docs/guides/commands/post-execution.md create mode 100644 docs/guides/commands/preconditions.md delete mode 100644 docs/guides/commands/samples/command_handler.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_map_setup.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_module.cs create mode 100644 docs/guides/commands/samples/dependency-injection/dependency_module_noinject.cs delete mode 100644 docs/guides/commands/samples/dependency_map_setup.cs delete mode 100644 docs/guides/commands/samples/dependency_module.cs delete mode 100644 docs/guides/commands/samples/empty-module.cs create mode 100644 docs/guides/commands/samples/intro/command_handler.cs create mode 100644 docs/guides/commands/samples/intro/empty-module.cs rename docs/guides/commands/samples/{ => intro}/groups.cs (52%) rename docs/guides/commands/samples/{ => intro}/module.cs (58%) create mode 100644 docs/guides/commands/samples/post-execution/command_exception_log.cs create mode 100644 docs/guides/commands/samples/post-execution/command_executed_adv_demo.cs create mode 100644 docs/guides/commands/samples/post-execution/command_executed_demo.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_base.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_extended.cs create mode 100644 docs/guides/commands/samples/post-execution/customresult_usage.cs create mode 100644 docs/guides/commands/samples/post-execution/post-execution_basic.cs create mode 100644 docs/guides/commands/samples/preconditions/group_precondition.cs create mode 100644 docs/guides/commands/samples/preconditions/precondition_usage.cs rename docs/guides/commands/samples/{ => preconditions}/require_owner.cs (66%) create mode 100644 docs/guides/commands/samples/typereaders/typereader-register.cs rename docs/guides/commands/samples/{ => typereaders}/typereader.cs (74%) create mode 100644 docs/guides/commands/typereaders.md create mode 100644 docs/guides/deployment/deployment.md create mode 100644 docs/guides/emoji/emoji.md create mode 100644 docs/guides/emoji/images/emojipedia.png create mode 100644 docs/guides/emoji/images/emote-format.png create mode 100644 docs/guides/emoji/images/fileformat-emoji-src.png create mode 100644 docs/guides/emoji/samples/emoji-sample.cs create mode 100644 docs/guides/emoji/samples/emote-sample.cs create mode 100644 docs/guides/emoji/samples/socket-emote-sample.cs create mode 100644 docs/guides/getting_started/first-bot.md create mode 100644 docs/guides/getting_started/images/appveyor-artifacts.png create mode 100644 docs/guides/getting_started/images/appveyor-nupkg.png create mode 100644 docs/guides/getting_started/images/intro-authorize.png create mode 100644 docs/guides/getting_started/images/intro-bot-settings.png delete mode 100644 docs/guides/getting_started/images/intro-client-id.png delete mode 100644 docs/guides/getting_started/images/intro-create-app.png delete mode 100644 docs/guides/getting_started/images/intro-create-bot.png create mode 100644 docs/guides/getting_started/images/intro-oauth-settings.png create mode 100644 docs/guides/getting_started/images/intro-public-bot.png create mode 100644 docs/guides/getting_started/images/intro-scopes-bot.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-note.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step1.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step2.png create mode 100644 docs/guides/getting_started/images/nightlies-vs-step4.png delete mode 100644 docs/guides/getting_started/intro.md create mode 100644 docs/guides/getting_started/nightlies.md create mode 100644 docs/guides/getting_started/samples/first-bot/async-context.cs create mode 100644 docs/guides/getting_started/samples/first-bot/client.cs create mode 100644 docs/guides/getting_started/samples/first-bot/complete.cs create mode 100644 docs/guides/getting_started/samples/first-bot/logging.cs rename docs/guides/getting_started/samples/{intro => first-bot}/message.cs (92%) rename docs/guides/getting_started/samples/{intro => first-bot}/structure.cs (100%) delete mode 100644 docs/guides/getting_started/samples/intro/async-context.cs delete mode 100644 docs/guides/getting_started/samples/intro/client.cs delete mode 100644 docs/guides/getting_started/samples/intro/complete.cs delete mode 100644 docs/guides/getting_started/samples/intro/logging.cs delete mode 100644 docs/guides/getting_started/samples/project.csproj create mode 100644 docs/guides/getting_started/samples/project.xml create mode 100644 docs/guides/introduction/intro.md delete mode 100644 docs/guides/migrating/migrating.md delete mode 100644 docs/guides/migrating/samples/event.cs delete mode 100644 docs/guides/migrating/samples/sync_event.cs create mode 100644 docs/langwordMapping.yml diff --git a/.gitignore b/.gitignore index 45e5e009d..954362408 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,6 @@ project.lock.json docs/api/\.manifest \.idea/ + +# Codealike UID +codealike.json \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8248291e8..752b40931 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,22 @@ We attempt to conform to the .NET Foundation's [Coding Style](https://github.com where possible. As a general rule, follow the coding style already set in the file you -are editing, or look at a similar file if you are adding a new one. \ No newline at end of file +are editing, or look at a similar file if you are adding a new one. + +### Documentation Style for Members + +When creating a new public member, the member must be annotated with sufficient documentation. This should include the +following, but not limited to: + +* `` summarizing the purpose of the method. +* `` or `` explaining the parameter. +* `` explaining the type of the returned member and what it is. +* `` if the method directly throws an exception. + +The length of the documentation should also follow the ruler as suggested by our +[Visual Studio Code workspace](Discord.Net.code-workspace). + +#### Recommended Reads + +* [Official Microsoft Documentation](https://docs.microsoft.com) +* [Sandcastle User Manual](https://ewsoftware.github.io/XMLCommentsGuide/html/4268757F-CE8D-4E6D-8502-4F7F2E22DDA3.htm) \ No newline at end of file diff --git a/Discord.Net.code-workspace b/Discord.Net.code-workspace new file mode 100644 index 000000000..709eb0e95 --- /dev/null +++ b/Discord.Net.code-workspace @@ -0,0 +1,23 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 120 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "docs/": true, + "**/obj": true, + "**/bin": true, + "samples/": true, + } + } +} \ No newline at end of file diff --git a/Discord.Net.sln.DotSettings b/Discord.Net.sln.DotSettings new file mode 100644 index 000000000..ca75a7f1b --- /dev/null +++ b/Discord.Net.sln.DotSettings @@ -0,0 +1,16 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 296b6d1cb..8641d377e 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,27 +1,21 @@ # Contributing to Docs -I don't really have any strict conditions for writing documentation, -but just keep these few guidelines in mind: +## General Guidelines + +We do not have any strict conditions for writing documentation, +but keep these guidelines in mind: * Keep code samples in the `guides/samples` folder -* When referencing an object in the API, link to it's page in the -API documentation. +* When referencing an object in the API, link to its page in the + API documentation +* Documentation should be written in an FAQ/Wiki-style format * Documentation should be written in clear and proper English* -\* If anyone is interested in translating documentation into other -languages, please open an issue or contact me on +\* If anyone is interested in translating documentation into other +languages, please open an issue or contact me on Discord (`foxbot#0282`). -### Layout - -Documentation should be written in a FAQ/Wiki style format. - -Recommended reads: - -* http://docs.microsoft.com -* http://flask.pocoo.org/docs/0.12/ - -Style consistencies: +## Style Consistencies * Use a ruler set at 70 characters * Links should use long syntax @@ -29,18 +23,13 @@ Style consistencies: Example of long link syntax: -``` +```md Please consult the [API Documentation] for more information. [API Documentation]: xref:System.String ``` -### Compiling - -Documentation is compiled into a static site using [DocFx]. -We currently use the most recent build off the dev branch. +## Recommended Reads -After making changes, compile your changes into the static site with -`docfx`. You can also view your changes live with `docfx --serve`. - -[DocFx]: https://dotnet.github.io/docfx/ \ No newline at end of file +* http://docs.microsoft.com +* http://flask.pocoo.org/docs/0.12/ \ No newline at end of file diff --git a/docs/Discord.Net.Docs.code-workspace b/docs/Discord.Net.Docs.code-workspace new file mode 100644 index 000000000..d9f442869 --- /dev/null +++ b/docs/Discord.Net.Docs.code-workspace @@ -0,0 +1,21 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.rulers": [ + 70 + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "obj/": true, + "_site/": true, + } + } +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index a672330d4..4a06dccab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,15 @@ # Instructions for Building Documentation -The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] +The documentation for the Discord.Net library uses [DocFX][docfx-main]. +Instructions for installing this tool can be found [here][docfx-installing]. 1. Navigate to the root of the repository. -2. (Optional) If you intend to target a specific version, ensure that you -have the correct version checked out. -3. Build the library. Run `dotnet build` in the root of this repository. - Ensure that the build passes without errors. -4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter -to preview the site locally. Some elements of the page may appear incorrect -when not hosted by a server. - - Remarks: According to the docfx website, this tool does work on Linux under mono. +2. Build the docs using `docfx docs/docfx.json`. Add the `--serve` + parameter to preview the site locally. Some elements of the page + may appear incorrectly when hosted offline. + +Please note that if you intend to target a specific version, ensure +that you have the correct version checked out. [docfx-main]: https://dotnet.github.io/docfx/ -[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html +[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html \ No newline at end of file diff --git a/docs/_overwrites/Commands/CommandException.Overwrite.md b/docs/_overwrites/Commands/CommandException.Overwrite.md new file mode 100644 index 000000000..166a011de --- /dev/null +++ b/docs/_overwrites/Commands/CommandException.Overwrite.md @@ -0,0 +1,31 @@ +--- +uid: Discord.Commands.CommandException +remarks: *content +--- + +This @System.Exception class is typically used when diagnosing +an error thrown during the execution of a command. You will find the +thrown exception passed into +[LogMessage.Exception](xref:Discord.LogMessage.Exception), which is +sent to your [CommandService.Log](xref:Discord.Commands.CommandService.Log) +event handler. + +--- +uid: Discord.Commands.CommandException +example: [*content] +--- + +You may use this information to handle runtime exceptions after +execution. Below is an example of how you may use this: + +```cs +public Task LogHandlerAsync(LogMessage logMessage) +{ + // Note that this casting method requires C#7 and up. + if (logMessage?.Exception is CommandException cmdEx) + { + Console.WriteLine($"{cmdEx.GetBaseException().GetType()} was thrown while executing {cmdEx.Command.Aliases.First()} in {cmdEx.Context.Channel} by {cmdEx.Context.User}."); + } + return Task.CompletedTask; +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md new file mode 100644 index 000000000..d47980df7 --- /dev/null +++ b/docs/_overwrites/Commands/DontAutoLoadAttribute.Overwrite.md @@ -0,0 +1,22 @@ +--- +uid: Discord.Commands.DontAutoLoadAttribute +remarks: *content +--- + +The attribute can be applied to a public class that inherits +@Discord.Commands.ModuleBase. By applying this attribute, +@Discord.Commands.CommandService.AddModulesAsync* will not discover and +add the marked module to the CommandService. + +--- +uid: Discord.Commands.DontAutoLoadAttribute +example: [*content] +--- + +```cs +[DontAutoLoad] +public class MyModule : ModuleBase +{ + // ... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md new file mode 100644 index 000000000..950d2990c --- /dev/null +++ b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.DontInjectAttribute +remarks: *content +--- + +The attribute can be applied to a public settable property inside a +@Discord.Commands.ModuleBase based class. By applying this attribute, +the marked property will not be automatically injected of the +dependency. See @Guides.Commands.DI to learn more. + +--- +uid: Discord.Commands.DontInjectAttribute +example: [*content] +--- + +```cs +public class MyModule : ModuleBase +{ + [DontInject] + public MyService MyService { get; set; } + + public MyModule() + { + MyService = new MyService(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Inclusion.md b/docs/_overwrites/Commands/ICommandContext.Inclusion.md new file mode 100644 index 000000000..4c1257b23 --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Inclusion.md @@ -0,0 +1,5 @@ +An example of how this class is used the command system can be seen +below: + +[!code[Sample module](../../guides/commands/samples/intro/empty-module.cs)] +[!code[Command handler](../../guides/commands/samples/intro/command_handler.cs)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/ICommandContext.Overwrite.md b/docs/_overwrites/Commands/ICommandContext.Overwrite.md new file mode 100644 index 000000000..d9e50b46d --- /dev/null +++ b/docs/_overwrites/Commands/ICommandContext.Overwrite.md @@ -0,0 +1,27 @@ +--- +uid: Discord.Commands.ICommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.CommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.SocketCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] + +--- +uid: Discord.Commands.ShardCommandContext +example: [*content] +--- + +[!include[Example Section](ICommandContext.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md new file mode 100644 index 000000000..75b9f93a5 --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Overwrites.md @@ -0,0 +1,103 @@ +--- +uid: Discord.Commands.PreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on module-level or +method-level for a command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +remarks: *content +--- + +This precondition attribute can be applied on parameter-level for a +command. + +[!include[Additional Remarks](PreconditionAttribute.Remarks.Inclusion.md)] + +--- +uid: Discord.Commands.PreconditionAttribute +example: [*content] +--- + +The following example creates a precondition to see if the user has +sufficient role required to access the command. + +```cs +public class RequireRoleAttribute : PreconditionAttribute +{ + private readonly ulong _roleId; + + public RequireRoleAttribute(ulong roleId) + { + _roleId = roleId; + } + + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) + { + var guildUser = context.User as IGuildUser; + if (guildUser == null) + return PreconditionResult.FromError("This command cannot be executed outside of a guild."); + + var guild = guildUser.Guild; + if (guild.Roles.All(r => r.Id != _roleId)) + return PreconditionResult.FromError( + $"The guild does not have the role ({_roleId}) required to access this command."); + + return guildUser.RoleIds.Any(rId => rId == _roleId) + ? PreconditionResult.FromSuccess() + : PreconditionResult.FromError("You do not have the sufficient role required to access this command."); + } +} +``` + +--- +uid: Discord.Commands.ParameterPreconditionAttribute +example: [*content] +--- + +The following example creates a precondition on a parameter-level to +see if the targeted user has a lower hierarchy than the user who +executed the command. + +```cs +public class RequireHierarchyAttribute : ParameterPreconditionAttribute +{ + public override async Task CheckPermissionsAsync(ICommandContext context, + ParameterInfo parameter, object value, IServiceProvider services) + { + // Hierarchy is only available under the socket variant of the user. + if (!(context.User is SocketGuildUser guildUser)) + return PreconditionResult.FromError("This command cannot be used outside of a guild."); + + SocketGuildUser targetUser; + switch (value) + { + case SocketGuildUser targetGuildUser: + targetUser = targetGuildUser; + break; + case ulong userId: + targetUser = await context.Guild.GetUserAsync(userId).ConfigureAwait(false) as SocketGuildUser; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (targetUser == null) + return PreconditionResult.FromError("Target user not found."); + + if (guildUser.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("You cannot target anyone else whose roles are higher than yours."); + + var currentUser = await context.Guild.GetCurrentUserAsync().ConfigureAwait(false) as SocketGuildUser; + if (currentUser?.Hierarchy < targetUser.Hierarchy) + return PreconditionResult.FromError("The bot's role is lower than the targeted user."); + + return PreconditionResult.FromSuccess(); + } +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md new file mode 100644 index 000000000..499cdb0ad --- /dev/null +++ b/docs/_overwrites/Commands/PreconditionAttribute.Remarks.Inclusion.md @@ -0,0 +1,6 @@ +A "precondidtion" in the command system is used to determine if a +condition is met before entering the command task. Using a +precondidtion may aid in keeping a well-organized command logic. + +The most common use case being whether a user has sufficient +permission to execute the command. \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md new file mode 100644 index 000000000..2dcb1e004 --- /dev/null +++ b/docs/_overwrites/Common/EmbedBuilder.Overwrites.md @@ -0,0 +1,68 @@ +--- +uid: Discord.EmbedBuilder +seealso: + - linkId: Discord.EmbedFooterBuilder + - linkId: Discord.EmbedAuthorBuilder + - linkId: Discord.EmbedFieldBuilder +remarks: *content +--- + +This builder class is used to build an @Discord.Embed (rich embed) +object that will be ready to be sent via @Discord.IMessageChannel.SendMessageAsync* +after @Discord.EmbedBuilder.Build* is called. + +--- +uid: Discord.EmbedBuilder +example: [*content] +--- + +#### Basic Usage + +The example below builds an embed and sends it to the chat using the +command system. + +```cs +[Command("embed")] +public async Task SendRichEmbedAsync() +{ + var embed = new EmbedBuilder + { + // Embed property can be set within object initializer + Title = "Hello world!" + Description = "I am a description set by initializer." + }; + // Or with methods + embed.AddField("Field title", + "Field value. I also support [hyperlink markdown](https://example.com)!") + .WithAuthor(Context.Client.CurrentUser) + .WithFooter(footer => footer.Text = "I am a footer.") + .WithColor(Color.Blue) + .WithTitle("I overwrote \"Hello world!\"") + .WithDescription("I am a description.") + .WithUrl("https://example.com") + .WithCurrentTimestamp() + .Build(); + await ReplyAsync(embed: embed); +} +``` + +![Embed Example](images/embed-example.png) + +#### Usage with Local Images + +The example below sends an image and has the image embedded in the rich +embed. See @Discord.IMessageChannel.SendFileAsync* for more information +about uploading a file or image. + +```cs +[Command("embedimage")] +public async Task SendEmbedWithImageAsync() +{ + var fileName = "image.png"; + var embed = new EmbedBuilder() + { + ImageUrl = $"attachment://{fileName}" + }.Build(); + await Context.Channel.SendFileAsync(fileName, embed: embed); +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md new file mode 100644 index 000000000..eac0d9ca5 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Inclusion.md @@ -0,0 +1,25 @@ +The example will build a rich embed with an author field, a footer +field, and 2 normal fields using an @Discord.EmbedBuilder: + +```cs +var exampleAuthor = new EmbedAuthorBuilder() + .WithName("I am a bot") + .WithIconUrl("https://discordapp.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png"); +var exampleFooter = new EmbedFooterBuilder() + .WithText("I am a nice footer") + .WithIconUrl("https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png"); +var exampleField = new EmbedFieldBuilder() + .WithName("Title of Another Field") + .WithValue("I am an [example](https://example.com).") + .WithInline(true); +var otherField = new EmbedFieldBuilder() + .WithName("Title of a Field") + .WithValue("Notice how I'm inline with that other field next to me.") + .WithInline(true); +var embed = new EmbedBuilder() + .AddField(exampleField) + .AddField(otherField) + .WithAuthor(exampleAuthor) + .WithFooter(exampleFooter) + .Build(); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md new file mode 100644 index 000000000..c633c29b1 --- /dev/null +++ b/docs/_overwrites/Common/EmbedObjectBuilder.Overwrites.md @@ -0,0 +1,20 @@ +--- +uid: Discord.EmbedAuthorBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFooterBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] + +--- +uid: Discord.EmbedFieldBuilder +example: [*content] +--- + +[!include[Embed Object Builder Sample](EmbedObjectBuilder.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Inclusion.md b/docs/_overwrites/Common/IEmote.Inclusion.md new file mode 100644 index 000000000..cf93c7eb5 --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Inclusion.md @@ -0,0 +1,26 @@ +The sample below sends a message and adds an @Discord.Emoji and a custom +@Discord.Emote to the message. + +```cs +public async Task SendAndReactAsync(ISocketMessageChannel channel) +{ + var message = await channel.SendMessageAsync("I am a message."); + + // Creates a Unicode-based emoji based on the Unicode string. + // This is effectively the same as new Emoji("💕"). + var heartEmoji = new Emoji("\U0001f495"); + // Reacts to the message with the Emoji. + await message.AddReactionAsync(heartEmoji); + + // Parses a custom emote based on the provided Discord emote format. + // Please note that this does not guarantee the existence of + // the emote. + var emote = Emote.Parse("<:thonkang:282745590985523200>"); + // Reacts to the message with the Emote. + await message.AddReactionAsync(emote); +} +``` + +#### Result + +![React Example](images/react-example.png) \ No newline at end of file diff --git a/docs/_overwrites/Common/IEmote.Overwrites.md b/docs/_overwrites/Common/IEmote.Overwrites.md new file mode 100644 index 000000000..034533d1d --- /dev/null +++ b/docs/_overwrites/Common/IEmote.Overwrites.md @@ -0,0 +1,81 @@ +--- +uid: Discord.IEmote +seealso: + - linkId: Discord.Emote + - linkId: Discord.Emoji + - linkId: Discord.GuildEmote + - linkId: Discord.IUserMessage +remarks: *content +--- + +This interface is often used with reactions. It can represent an +unicode-based @Discord.Emoji, or a custom @Discord.Emote. + +--- +uid: Discord.Emote +seealso: + - linkId: Discord.IEmote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emote format is `<:emoteName:emoteId>`. This can be +> obtained by escaping with a `\` in front of the emote using the +> Discord chat client. + +This class represents a custom emoji. This type of emoji can be +created via the @Discord.Emote.Parse* or @Discord.Emote.TryParse* +method. + +--- +uid: Discord.Emoji +seealso: + - linkId: Discord.Emote + - linkId: Discord.GuildEmote + - linkId: Discord.Emoji + - linkId: Discord.IUserMessage +remarks: *content +--- + +> [!NOTE] +> A valid @Discord.Emoji format is Unicode-based. This means only +> something like `🙃` or `\U0001f643` would work, instead of +> `:upside_down:`. +> +> A Unicode-based emoji can be obtained by escaping with a `\` in +> front of the emote using the Discord chat client or by looking up on +> [Emojipedia](https://emojipedia.org). + +This class represents a standard Unicode-based emoji. This type of emoji +can be created by passing the Unicode into the constructor. + +--- +uid: Discord.IEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emoji +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.Emote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] + +--- +uid: Discord.GuildEmote +example: [*content] +--- + +[!include[Example Section](IEmote.Inclusion.md)] \ No newline at end of file diff --git a/docs/_overwrites/Common/ObjectProperties.Overwrites.md b/docs/_overwrites/Common/ObjectProperties.Overwrites.md new file mode 100644 index 000000000..e9c365d39 --- /dev/null +++ b/docs/_overwrites/Common/ObjectProperties.Overwrites.md @@ -0,0 +1,174 @@ +--- +uid: Discord.GuildChannelProperties +example: [*content] +--- + +The following example uses @Discord.IGuildChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IGuildChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "new-name"; + x.Position = channel.Position - 1; +}); +``` + +--- +uid: Discord.TextChannelProperties +example: [*content] +--- + +The following example uses @Discord.ITextChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as ITextChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.Name = "cool-guys-only"; + x.Topic = "This channel is only for cool guys and adults!!!"; + x.Position = channel.Position - 1; + x.IsNsfw = true; +}); +``` + +--- +uid: Discord.VoiceChannelProperties +example: [*content] +--- + +The following example uses @Discord.IVoiceChannel.ModifyAsync* to +apply changes specified in the properties, + +```cs +var channel = _client.GetChannel(id) as IVoiceChannel; +if (channel == null) return; + +await channel.ModifyAsync(x => +{ + x.UserLimit = 5; +}); +``` + +--- +uid: Discord.EmoteProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyEmoteAsync* to +apply changes specified in the properties, + +```cs +await guild.ModifyEmoteAsync(x => +{ + x.Name = "blobo"; +}); +``` + +--- +uid: Discord.MessageProperties +example: [*content] +--- + +The following example uses @Discord.IUserMessage.ModifyAsync* to +apply changes specified in the properties, + +```cs +var message = await channel.SendMessageAsync("boo"); +await Task.Delay(TimeSpan.FromSeconds(1)); +await message.ModifyAsync(x => x.Content = "boi"); +``` + +--- +uid: Discord.GuildProperties +example: [*content] +--- + +The following example uses @Discord.IGuild.ModifyAsync* to +apply changes specified in the properties, + +```cs +var guild = _client.GetGuild(id); +if (guild == null) return; + +await guild.ModifyAsync(x => +{ + x.Name = "VERY Fast Discord Running at Incredible Hihg Speed"; +}); +``` + +--- +uid: Discord.RoleProperties +example: [*content] +--- + +The following example uses @Discord.IRole.ModifyAsync* to +apply changes specified in the properties, + +```cs +var role = guild.GetRole(id); +if (role == null) return; + +await role.ModifyAsync(x => +{ + x.Name = "cool boi"; + x.Color = Color.Gold; + x.Hoist = true; + x.Mentionable = true; +}); +``` + +--- +uid: Discord.GuildUserProperties +example: [*content] +--- + +The following example uses @Discord.IGuildUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +var user = guild.GetUser(id); +if (user == null) return; + +await user.ModifyAsync(x => +{ + x.Nickname = "I need healing"; +}); +``` + +--- +uid: Discord.SelfUserProperties +example: [*content] +--- + +The following example uses @Discord.ISelfUser.ModifyAsync* to +apply changes specified in the properties, + +```cs +await selfUser.ModifyAsync(x => +{ + x.Username = "Mercy"; +}); +``` + +--- +uid: Discord.WebhookProperties +example: [*content] +--- + +The following example uses @Discord.IWebhook.ModifyAsync* to +apply changes specified in the properties, + +```cs +await webhook.ModifyAsync(x => +{ + x.Name = "very fast fox"; + x.ChannelId = newChannelId; +}); +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md new file mode 100644 index 000000000..29b547e49 --- /dev/null +++ b/docs/_overwrites/Common/OverrideTypeReaderAttribute.Overwrites.md @@ -0,0 +1,24 @@ +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +remarks: *content +--- + +This attribute is used to override a command parameter's type reading +behaviour. This may be useful when you have multiple custom +@Discord.Commands.TypeReader and would like to specify one. + +--- +uid: Discord.Commands.OverrideTypeReaderAttribute +examples: [*content] +--- + +The following example will override the @Discord.Commands.TypeReader +of @Discord.IUser to `MyUserTypeReader`. + +```cs +public async Task PrintUserAsync( + [OverrideTypeReader(typeof(MyUserTypeReader))] IUser user) +{ + //... +} +``` \ No newline at end of file diff --git a/docs/_overwrites/Common/images/embed-example.png b/docs/_overwrites/Common/images/embed-example.png new file mode 100644 index 0000000000000000000000000000000000000000..f23fb4d70d797dd65e40fae54e84cd1935dd8608 GIT binary patch literal 15290 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 IWt)5d2bHp1(f|Me literal 0 HcmV?d00001 diff --git a/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll b/docs/_template/description-generator/plugins/DocFX.Plugin.DescriptionGenerator.dll new file mode 100644 index 0000000000000000000000000000000000000000..3e3a6fb7c829f3fb32d11d8184600bc557e6c7ad GIT binary patch literal 9216 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 LzvcY@?t%XV?WM^? literal 0 HcmV?d00001 diff --git a/docs/_template/description-generator/plugins/LICENSE b/docs/_template/description-generator/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/description-generator/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LICENSE b/docs/_template/last-modified/plugins/LICENSE new file mode 100644 index 000000000..eb92c0a03 --- /dev/null +++ b/docs/_template/last-modified/plugins/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Still Hsu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll b/docs/_template/last-modified/plugins/LastModifiedPostProcessor.dll new file mode 100644 index 0000000000000000000000000000000000000000..d91cf08737e9e605e894d2c405a369f5966a9ef6 GIT binary patch literal 11776 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 xEguALId@$chtI(Ud3>8eEUw-s$e|3