From 94a0d1651bc2a3dd261aba070090e301b44b0e18 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Sat, 20 May 2023 15:23:15 +0200 Subject: [PATCH 01/21] fix events blocking the gateway --- DSharpPlus/AsyncEvents/AsyncEvent`2.cs | 33 +++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/DSharpPlus/AsyncEvents/AsyncEvent`2.cs b/DSharpPlus/AsyncEvents/AsyncEvent`2.cs index bb3f8490fc..ecc7af4f46 100644 --- a/DSharpPlus/AsyncEvents/AsyncEvent`2.cs +++ b/DSharpPlus/AsyncEvents/AsyncEvent`2.cs @@ -30,6 +30,7 @@ namespace DSharpPlus.AsyncEvents { + /// /// Provides an implementation of an asynchronous event. Registered handlers are executed asynchronously, /// in parallel, and potential exceptions are caught and sent to the specified exception handler. @@ -57,7 +58,9 @@ public AsyncEvent(string name, AsyncEventExceptionHandler except public void Register(AsyncEventHandler handler) { if (handler is null) + { throw new ArgumentNullException(nameof(handler)); + } this._lock.Wait(); try @@ -77,8 +80,9 @@ public void Register(AsyncEventHandler handler) public void Unregister(AsyncEventHandler handler) { if (handler is null) + { throw new ArgumentNullException(nameof(handler)); - + } this._lock.Wait(); try @@ -105,30 +109,25 @@ public void UnregisterAll() public async Task InvokeAsync(TSender sender, TArgs args) { if (this._handlers.Count == 0) + { return; + } await this._lock.WaitAsync(); List> copiedHandlers = new(this._handlers); this._lock.Release(); - try + _ = Task.WhenAll(copiedHandlers.Select(async (handler) => { - await Task.WhenAll(copiedHandlers.Select(async (handler) => + try { - try - { - await handler(sender, args); - } - catch (Exception ex) - { - this._exceptionHandler?.Invoke(this, ex, handler, sender, args); - } - })); - } - finally - { - this._lock.Release(); - } + await handler(sender, args); + } + catch (Exception ex) + { + this._exceptionHandler?.Invoke(this, ex, handler, sender, args); + } + })); return; } From 70c88e5c419f2bf125437b825989bd1c3a2b53cb Mon Sep 17 00:00:00 2001 From: InF Date: Sat, 20 May 2023 22:47:13 +0500 Subject: [PATCH 02/21] update release action to work properly with release branches --- .github/workflows/publish_release_master.yml | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index 5da43bb075..df1a5cd0fb 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -2,14 +2,18 @@ name: "Publish Release" on: release: types: ["published"] + env: - DOTNET_NOLOGO: true - DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1 + jobs: publish_release: - name: Publish Release (Master) + name: Publish Release runs-on: "self-hosted" - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master')" + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master') && contains(github.event.release.target_commitish, 'release/*'" steps: - name: Checkout uses: actions/checkout@v3 @@ -19,16 +23,16 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 7.0.103 - - name: Build Nuget Packages + - name: Build NuGet packages run: "mkdir build && dotnet pack --include-symbols -p:SymbolPackageFormat=snupkg -c Release -o build" - - name: Publish Nuget Packages + - name: Publish NuGet packages run: "dotnet nuget push \"build/*\" -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json" - - name: Upload Nuget Packages To Github Actions + - name: Upload NuGet packages to GitHub Actions uses: actions/upload-artifact@v3 with: - name: PR Nuget Packages + name: PR NuGet Packages path: build/* - - name: Upload Nuget Packages To Github Release + - name: Upload NuGet Packages To Github Release uses: "ncipollo/release-action@v1" with: allowUpdates: true @@ -37,8 +41,8 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} omitBodyDuringUpdate: true # We don't want to update the body of the release. omitNameDuringUpdate: true # We don't want to update the name of the release. - - name: Export Latest Tag - run: echo LATEST_STABLE_VERSION=$(git describe --abbrev=0 --tags) >> $GITHUB_ENV + - name: Export latest tag + run: echo LATEST_STABLE_VERSION=$(git describe --tags $(git rev-list --tags --max-count=1)) >> $GITHUB_ENV - name: Update Discord Channel Topic run: "dotnet run --project ./tools/AutoUpdateChannelDescription -p:Nightly=$(printf \"%0*d\n\" 5 $(( 1195 + ${{ github.run_number }} ))" env: From b3771e330e7a21f4add41f0b7bcb38348f90de85 Mon Sep 17 00:00:00 2001 From: InFTord Date: Sat, 20 May 2023 22:56:41 +0500 Subject: [PATCH 03/21] the most stable DSharpPlus release ever --- DSharpPlus.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 168293733f..9315078d9e 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.0 + 4.4.1 1591 9.0 True From 1f5756d022ff3395df4b17fdf0619a2e2855a8df Mon Sep 17 00:00:00 2001 From: InFTord Date: Sat, 20 May 2023 23:37:29 +0500 Subject: [PATCH 04/21] change the runner to GitHub provided --- .github/workflows/publish_release_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index df1a5cd0fb..c984987f3a 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -12,7 +12,7 @@ env: jobs: publish_release: name: Publish Release - runs-on: "self-hosted" + runs-on: "ubuntu-latest" if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master') && contains(github.event.release.target_commitish, 'release/*'" steps: - name: Checkout From a203306bd71f0292d856cbf033b99d136f4eab69 Mon Sep 17 00:00:00 2001 From: InFTord Date: Sun, 21 May 2023 00:13:56 +0500 Subject: [PATCH 05/21] please work --- .github/workflows/publish_release_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index c984987f3a..4743ba3404 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -13,7 +13,7 @@ jobs: publish_release: name: Publish Release runs-on: "ubuntu-latest" - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master') && contains(github.event.release.target_commitish, 'release/*'" + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master') && contains(github.event.release.target_commitish, 'release/**')" steps: - name: Checkout uses: actions/checkout@v3 From 8959f0a2da6a9ead20780f5645ea83258c69b0cf Mon Sep 17 00:00:00 2001 From: InFTord Date: Sun, 21 May 2023 00:17:02 +0500 Subject: [PATCH 06/21] remove the master check because we don't really need it --- .github/workflows/publish_release_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index 4743ba3404..7fc1cc9f60 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -13,7 +13,7 @@ jobs: publish_release: name: Publish Release runs-on: "ubuntu-latest" - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]') && contains(github.event.release.target_commitish, 'master') && contains(github.event.release.target_commitish, 'release/**')" + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]'))" steps: - name: Checkout uses: actions/checkout@v3 From 6594609a741c66d0cc013edc138a56e601466f26 Mon Sep 17 00:00:00 2001 From: InFTord Date: Sun, 21 May 2023 00:19:48 +0500 Subject: [PATCH 07/21] ok i really need go to sleep. --- .github/workflows/publish_release_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index 7fc1cc9f60..d734e4593e 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -13,7 +13,7 @@ jobs: publish_release: name: Publish Release runs-on: "ubuntu-latest" - if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]'))" + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" steps: - name: Checkout uses: actions/checkout@v3 From 4dc6459170bbd6032b27e94f7296fb626fac1902 Mon Sep 17 00:00:00 2001 From: Kotz <1812311+Kaoticz@users.noreply.github.com> Date: Tue, 13 Jun 2023 06:30:59 -0300 Subject: [PATCH 08/21] No more NREs when disposing/finalizing, fixes #1547 (#1567) * Fixed NREs when disposing/finalizing.. * Some additional safeguards, just in case * Prevent disposed sharded clients from throwing InvalidOperationException in the GC --- .../CommandsNextExtension.cs | 8 ++-- .../EventHandling/EventWaiter.cs | 24 +++++----- .../EventHandling/Paginator.cs | 23 +++++----- .../EventHandling/Poller.cs | 26 ++++++----- .../EventHandling/ReactionCollector.cs | 46 +++++++++---------- .../EventHandling/Requests/CollectRequest.cs | 11 ++--- .../EventHandling/Requests/MatchRequest.cs | 14 +++--- .../Requests/PaginationRequest.cs | 11 ++--- .../EventHandling/Requests/PollRequest.cs | 11 ++--- .../InteractivityExtension.cs | 28 ++++++----- DSharpPlus.Lavalink/LavalinkExtension.cs | 15 +++--- DSharpPlus.Rest/DiscordRestClient.cs | 2 +- .../SlashCommandsExtension.cs | 33 ++++++------- DSharpPlus.VoiceNext/VoiceNextConnection.cs | 23 ++++------ DSharpPlus.VoiceNext/VoiceNextExtension.cs | 18 ++++---- DSharpPlus/Clients/DiscordClient.cs | 25 +++++----- DSharpPlus/Clients/DiscordShardedClient.cs | 9 ++-- DSharpPlus/Clients/DiscordWebhookClient.cs | 4 +- DSharpPlus/Net/Rest/RestClient.cs | 11 ++--- 19 files changed, 163 insertions(+), 179 deletions(-) diff --git a/DSharpPlus.CommandsNext/CommandsNextExtension.cs b/DSharpPlus.CommandsNext/CommandsNextExtension.cs index fc52c0775a..06bf12fea8 100644 --- a/DSharpPlus.CommandsNext/CommandsNextExtension.cs +++ b/DSharpPlus.CommandsNext/CommandsNextExtension.cs @@ -166,11 +166,11 @@ internal CommandsNextExtension(CommandsNextConfiguration cfg) /// Disposes of this the resources used by CNext. /// public override void Dispose() - => this.Config.CommandExecutor.Dispose(); - - ~CommandsNextExtension() { - this.Dispose(); + this.Config.CommandExecutor.Dispose(); + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } #region DiscordClient Registration diff --git a/DSharpPlus.Interactivity/EventHandling/EventWaiter.cs b/DSharpPlus.Interactivity/EventHandling/EventWaiter.cs index 2fd04f61d9..606f957977 100644 --- a/DSharpPlus.Interactivity/EventHandling/EventWaiter.cs +++ b/DSharpPlus.Interactivity/EventHandling/EventWaiter.cs @@ -133,31 +133,33 @@ private Task HandleEvent(DiscordClient client, T eventargs) return Task.CompletedTask; } - ~EventWaiter() - { - this.Dispose(); - } - /// /// Disposes this EventWaiter /// public void Dispose() { + if (this._disposed) + return; + this._disposed = true; - if (this._event != null) + + if (this._event != null && this._handler != null) this._event.Unregister(this._handler); - this._event = null; - this._handler = null; - this._client = null; + this._event = null!; + this._handler = null!; + this._client = null!; if (this._matchrequests != null) this._matchrequests.Clear(); if (this._collectrequests != null) this._collectrequests.Clear(); - this._matchrequests = null; - this._collectrequests = null; + this._matchrequests = null!; + this._collectrequests = null!; + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } } diff --git a/DSharpPlus.Interactivity/EventHandling/Paginator.cs b/DSharpPlus.Interactivity/EventHandling/Paginator.cs index 65c7f0d018..9d9d27548b 100644 --- a/DSharpPlus.Interactivity/EventHandling/Paginator.cs +++ b/DSharpPlus.Interactivity/EventHandling/Paginator.cs @@ -260,22 +260,23 @@ private async Task PaginateAsync(IPaginationRequest p, DiscordEmoji emoji) await builder.ModifyAsync(msg).ConfigureAwait(false); } - ~Paginator() - { - this.Dispose(); - } - /// /// Disposes this EventWaiter /// public void Dispose() { - this._client.MessageReactionAdded -= this.HandleReactionAdd; - this._client.MessageReactionRemoved -= this.HandleReactionRemove; - this._client.MessageReactionsCleared -= this.HandleReactionClear; - this._client = null; - this._requests.Clear(); - this._requests = null; + // Why doesn't this class implement IDisposable? + + if (this._client != null) + { + this._client.MessageReactionAdded -= this.HandleReactionAdd; + this._client.MessageReactionRemoved -= this.HandleReactionRemove; + this._client.MessageReactionsCleared -= this.HandleReactionClear; + this._client = null!; + } + + this._requests?.Clear(); + this._requests = null!; } } } diff --git a/DSharpPlus.Interactivity/EventHandling/Poller.cs b/DSharpPlus.Interactivity/EventHandling/Poller.cs index af86b2300e..0caa81a4c7 100644 --- a/DSharpPlus.Interactivity/EventHandling/Poller.cs +++ b/DSharpPlus.Interactivity/EventHandling/Poller.cs @@ -128,22 +128,26 @@ private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEven return Task.CompletedTask; } - ~Poller() - { - this.Dispose(); - } - /// /// Disposes this EventWaiter /// public void Dispose() { - this._client.MessageReactionAdded -= this.HandleReactionAdd; - this._client.MessageReactionRemoved -= this.HandleReactionRemove; - this._client.MessageReactionsCleared -= this.HandleReactionClear; - this._client = null; - this._requests.Clear(); - this._requests = null; + // Why doesn't this class implement IDisposable? + + if (this._client != null) + { + this._client.MessageReactionAdded -= this.HandleReactionAdd; + this._client.MessageReactionRemoved -= this.HandleReactionRemove; + this._client.MessageReactionsCleared -= this.HandleReactionClear; + this._client = null!; + } + + if (this._requests != null) + { + this._requests.Clear(); + this._requests = null!; + } } } } diff --git a/DSharpPlus.Interactivity/EventHandling/ReactionCollector.cs b/DSharpPlus.Interactivity/EventHandling/ReactionCollector.cs index b229d238e3..b7938549e9 100644 --- a/DSharpPlus.Interactivity/EventHandling/ReactionCollector.cs +++ b/DSharpPlus.Interactivity/EventHandling/ReactionCollector.cs @@ -168,31 +168,34 @@ private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEven return Task.CompletedTask; } - ~ReactionCollector() - { - this.Dispose(); - } - /// /// Disposes this EventWaiter /// public void Dispose() { - this._client = null; + this._client = null!; + + if (this._reactionAddHandler != null) + this._reactionAddEvent?.Unregister(this._reactionAddHandler); - this._reactionAddEvent.Unregister(this._reactionAddHandler); - this._reactionRemoveEvent.Unregister(this._reactionRemoveHandler); - this._reactionClearEvent.Unregister(this._reactionClearHandler); + if (this._reactionRemoveHandler != null) + this._reactionRemoveEvent?.Unregister(this._reactionRemoveHandler); - this._reactionAddEvent = null; - this._reactionAddHandler = null; - this._reactionRemoveEvent = null; - this._reactionRemoveHandler = null; - this._reactionClearEvent = null; - this._reactionClearHandler = null; + if (this._reactionClearHandler != null) + this._reactionClearEvent?.Unregister(this._reactionClearHandler); - this._requests.Clear(); - this._requests = null; + this._reactionAddEvent = null!; + this._reactionAddHandler = null!; + this._reactionRemoveEvent = null!; + this._reactionRemoveHandler = null!; + this._reactionClearEvent = null!; + this._reactionClearHandler = null!; + + this._requests?.Clear(); + this._requests = null!; + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } @@ -214,19 +217,16 @@ public ReactionCollectRequest(DiscordMessage msg, TimeSpan timeout) this._ct.Token.Register(() => this._tcs.TrySetResult(null)); } - ~ReactionCollectRequest() - { - this.Dispose(); - } - public void Dispose() { - GC.SuppressFinalize(this); this._ct.Dispose(); this._tcs = null; this._message = null; this._collected?.Clear(); this._collected = null; + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } diff --git a/DSharpPlus.Interactivity/EventHandling/Requests/CollectRequest.cs b/DSharpPlus.Interactivity/EventHandling/Requests/CollectRequest.cs index 013fd1836e..2e25649904 100644 --- a/DSharpPlus.Interactivity/EventHandling/Requests/CollectRequest.cs +++ b/DSharpPlus.Interactivity/EventHandling/Requests/CollectRequest.cs @@ -57,24 +57,19 @@ public CollectRequest(Func predicate, TimeSpan timeout) this._collected = new ConcurrentHashSet(); } - ~CollectRequest() - { - this.Dispose(); - } - /// /// Disposes this CollectRequest. /// public void Dispose() { this._ct.Dispose(); - this._tcs = null; - this._predicate = null; + this._tcs = null!; + this._predicate = null!; if (this._collected != null) { this._collected.Clear(); - this._collected = null; + this._collected = null!; } } } diff --git a/DSharpPlus.Interactivity/EventHandling/Requests/MatchRequest.cs b/DSharpPlus.Interactivity/EventHandling/Requests/MatchRequest.cs index 08a812a95f..f1d6387910 100644 --- a/DSharpPlus.Interactivity/EventHandling/Requests/MatchRequest.cs +++ b/DSharpPlus.Interactivity/EventHandling/Requests/MatchRequest.cs @@ -54,19 +54,17 @@ public MatchRequest(Func predicate, TimeSpan timeout) this._timeout = timeout; } - ~MatchRequest() - { - this.Dispose(); - } - /// /// Disposes this MatchRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; - this._predicate = null; + this._ct?.Dispose(); + this._tcs = null!; + this._predicate = null!; + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } } diff --git a/DSharpPlus.Interactivity/EventHandling/Requests/PaginationRequest.cs b/DSharpPlus.Interactivity/EventHandling/Requests/PaginationRequest.cs index e293819026..2033a04079 100644 --- a/DSharpPlus.Interactivity/EventHandling/Requests/PaginationRequest.cs +++ b/DSharpPlus.Interactivity/EventHandling/Requests/PaginationRequest.cs @@ -195,18 +195,15 @@ public async Task> GetTaskCompletionSourceAsync() return this._tcs; } - ~PaginationRequest() - { - this.Dispose(); - } - /// /// Disposes this PaginationRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; + // Why doesn't this class implement IDisposable? + + this._ct?.Dispose(); + this._tcs = null!; } } } diff --git a/DSharpPlus.Interactivity/EventHandling/Requests/PollRequest.cs b/DSharpPlus.Interactivity/EventHandling/Requests/PollRequest.cs index 3b8b195eaa..3c1959d61e 100644 --- a/DSharpPlus.Interactivity/EventHandling/Requests/PollRequest.cs +++ b/DSharpPlus.Interactivity/EventHandling/Requests/PollRequest.cs @@ -99,18 +99,15 @@ internal void AddReaction(DiscordEmoji emoji, DiscordUser member) } } - ~PollRequest() - { - this.Dispose(); - } - /// /// Disposes this PollRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; + // Why doesn't this class implement IDisposable? + + this._ct?.Dispose(); + this._tcs = null!; } } diff --git a/DSharpPlus.Interactivity/InteractivityExtension.cs b/DSharpPlus.Interactivity/InteractivityExtension.cs index c458667c7e..906eb109ac 100644 --- a/DSharpPlus.Interactivity/InteractivityExtension.cs +++ b/DSharpPlus.Interactivity/InteractivityExtension.cs @@ -1060,21 +1060,19 @@ private async Task HandleInvalidInteraction(DiscordInteraction interaction) public override void Dispose() { - this.ComponentEventWaiter.Dispose(); - this.ModalEventWaiter.Dispose(); - this.ReactionCollector.Dispose(); - this.ComponentInteractionWaiter.Dispose(); - this.MessageCreatedWaiter.Dispose(); - this.MessageReactionAddWaiter.Dispose(); - this.Paginator.Dispose(); - this.Poller.Dispose(); - this.TypingStartWaiter.Dispose(); - this._compPaginator.Dispose(); - } - - ~InteractivityExtension() - { - this.Dispose(); + this.ComponentEventWaiter?.Dispose(); + this.ModalEventWaiter?.Dispose(); + this.ReactionCollector?.Dispose(); + this.ComponentInteractionWaiter?.Dispose(); + this.MessageCreatedWaiter?.Dispose(); + this.MessageReactionAddWaiter?.Dispose(); + this.Paginator?.Dispose(); + this.Poller?.Dispose(); + this.TypingStartWaiter?.Dispose(); + this._compPaginator?.Dispose(); + + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } } diff --git a/DSharpPlus.Lavalink/LavalinkExtension.cs b/DSharpPlus.Lavalink/LavalinkExtension.cs index 7e16fe51b0..daefcfe752 100644 --- a/DSharpPlus.Lavalink/LavalinkExtension.cs +++ b/DSharpPlus.Lavalink/LavalinkExtension.cs @@ -199,22 +199,19 @@ private Task Con_Disconnected(LavalinkNodeConnection node, NodeDisconnectedEvent public override void Dispose() { - foreach(var node in this._connectedNodes) + foreach (var node in this._connectedNodes) { // undoubtedly there will be some GitHub comments about this. Help. node.Value.StopAsync().GetAwaiter().GetResult(); } - this._connectedNodes.Clear(); - // unhook events - _nodeDisconnected.UnregisterAll(); + this._connectedNodes?.Clear(); - // Hi GC! <3 😘 clean me up uwu - } + // unhook events + this._nodeDisconnected?.UnregisterAll(); - ~LavalinkExtension() - { - this.Dispose(); + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } } diff --git a/DSharpPlus.Rest/DiscordRestClient.cs b/DSharpPlus.Rest/DiscordRestClient.cs index d4c569c33d..e48e9bf1fc 100644 --- a/DSharpPlus.Rest/DiscordRestClient.cs +++ b/DSharpPlus.Rest/DiscordRestClient.cs @@ -2293,7 +2293,7 @@ public override void Dispose() return; this._disposed = true; this._guilds = null; - this.ApiClient._rest.Dispose(); + this.ApiClient?._rest?.Dispose(); } } } diff --git a/DSharpPlus.SlashCommands/SlashCommandsExtension.cs b/DSharpPlus.SlashCommands/SlashCommandsExtension.cs index 5ffec91ddb..93e81035fd 100644 --- a/DSharpPlus.SlashCommands/SlashCommandsExtension.cs +++ b/DSharpPlus.SlashCommands/SlashCommandsExtension.cs @@ -1238,23 +1238,24 @@ public event AsyncEventHandler /// Connects to the specified voice channel. /// @@ -724,15 +719,15 @@ public void Dispose() this.IsDisposed = true; this.IsInitialized = false; - this.TokenSource.Cancel(); - this.SenderTokenSource.Cancel(); + this.TokenSource?.Cancel(); + this.SenderTokenSource?.Cancel(); this.ReceiverTokenSource?.Cancel(); - this.KeepaliveTokenSource.Cancel(); + this.KeepaliveTokenSource?.Cancel(); - this.TokenSource.Dispose(); - this.SenderTokenSource.Dispose(); + this.TokenSource?.Dispose(); + this.SenderTokenSource?.Dispose(); this.ReceiverTokenSource?.Dispose(); - this.KeepaliveTokenSource.Dispose(); + this.KeepaliveTokenSource?.Dispose(); try { @@ -742,11 +737,11 @@ public void Dispose() catch { } this.Opus?.Dispose(); - this.Opus = null; + this.Opus = null!; this.Sodium?.Dispose(); - this.Sodium = null; + this.Sodium = null!; this.Rtp?.Dispose(); - this.Rtp = null; + this.Rtp = null!; this.VoiceDisconnected?.Invoke(this.Guild); } diff --git a/DSharpPlus.VoiceNext/VoiceNextExtension.cs b/DSharpPlus.VoiceNext/VoiceNextExtension.cs index b96d7e7ca9..cdb1a014fd 100644 --- a/DSharpPlus.VoiceNext/VoiceNextExtension.cs +++ b/DSharpPlus.VoiceNext/VoiceNextExtension.cs @@ -229,18 +229,20 @@ private async Task Client_VoiceServerUpdate(DiscordClient client, VoiceServerUpd public override void Dispose() { - foreach(var conn in this.ActiveConnections) + foreach (var conn in this.ActiveConnections) { - conn.Value.Dispose(); + conn.Value?.Dispose(); + } + + if (this.Client != null) + { + this.Client.VoiceStateUpdated -= this.Client_VoiceStateUpdate; + this.Client.VoiceServerUpdated -= this.Client_VoiceServerUpdate; } - this.Client.VoiceStateUpdated -= this.Client_VoiceStateUpdate; - this.Client.VoiceServerUpdated -= this.Client_VoiceServerUpdate; // Lo and behold, the audacious man who dared lay his hand upon VoiceNext hath once more trespassed upon its profane ground! - } - ~VoiceNextExtension() - { - this.Dispose(); + // Satisfy rule CA1816. Can be removed if this class is sealed. + GC.SuppressFinalize(this); } } } diff --git a/DSharpPlus/Clients/DiscordClient.cs b/DSharpPlus/Clients/DiscordClient.cs index 180bd93b03..38ff52f0ba 100644 --- a/DSharpPlus/Clients/DiscordClient.cs +++ b/DSharpPlus/Clients/DiscordClient.cs @@ -1030,13 +1030,8 @@ private void PopulateMessageReactionsAndCache(DiscordMessage message, TransportU #region Disposal - ~DiscordClient() - { - this.Dispose(); - } - - private bool _disposed; + /// /// Disposes your DiscordClient. /// @@ -1046,17 +1041,19 @@ public override void Dispose() return; this._disposed = true; - GC.SuppressFinalize(this); - this.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - this.ApiClient._rest.Dispose(); - this.CurrentUser = null; + this.DisconnectAsync().GetAwaiter().GetResult(); + this.ApiClient?._rest?.Dispose(); + this.CurrentUser = null!; var extensions = this._extensions; // prevent _extensions being modified during dispose - this._extensions = null; + this._extensions = null!; + foreach (var extension in extensions) + { if (extension is IDisposable disposable) disposable.Dispose(); + } try { @@ -1065,9 +1062,9 @@ public override void Dispose() } catch { } - this._guilds = null; - this._heartbeatTask = null; - this._privateChannels = null; + this._guilds = null!; + this._heartbeatTask = null!; + this._privateChannels = null!; } #endregion diff --git a/DSharpPlus/Clients/DiscordShardedClient.cs b/DSharpPlus/Clients/DiscordShardedClient.cs index 25d80fa124..2545110e99 100644 --- a/DSharpPlus/Clients/DiscordShardedClient.cs +++ b/DSharpPlus/Clients/DiscordShardedClient.cs @@ -402,12 +402,12 @@ private Task InternalStopAsync(bool enableLogger = true) if (!this._isStarted) throw new InvalidOperationException("This client has not been started."); + this._isStarted = false; + if (enableLogger) this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disposing {ShardCount} shards.", this._shards.Count); - this._isStarted = false; this._voiceRegionsLazy = null; - this.GatewayInfo = null; this.CurrentUser = null; this.CurrentApplication = null; @@ -671,7 +671,10 @@ private int GetShardIdFromGuilds(ulong id) #region Destructor ~DiscordShardedClient() - => this.InternalStopAsync(false).GetAwaiter().GetResult(); + { + if (this._isStarted) + this.InternalStopAsync(false).GetAwaiter().GetResult(); + } #endregion } diff --git a/DSharpPlus/Clients/DiscordWebhookClient.cs b/DSharpPlus/Clients/DiscordWebhookClient.cs index 869404f098..6e6823aa51 100644 --- a/DSharpPlus/Clients/DiscordWebhookClient.cs +++ b/DSharpPlus/Clients/DiscordWebhookClient.cs @@ -272,8 +272,8 @@ public async Task> BroadcastMessageAs ~DiscordWebhookClient() { - this._hooks.Clear(); - this._hooks = null; + this._hooks?.Clear(); + this._hooks = null!; this._apiclient._rest.Dispose(); } } diff --git a/DSharpPlus/Net/Rest/RestClient.cs b/DSharpPlus/Net/Rest/RestClient.cs index abed35bb40..155de7f5ed 100644 --- a/DSharpPlus/Net/Rest/RestClient.cs +++ b/DSharpPlus/Net/Rest/RestClient.cs @@ -677,9 +677,6 @@ private async Task CleanupBucketsAsync() this.Logger.LogDebug(LoggerEvents.RestCleaner, "Bucket cleaner task stopped."); } - ~RestClient() - => this.Dispose(); - public void Dispose() { if (this._disposed) @@ -687,7 +684,7 @@ public void Dispose() this._disposed = true; - this.GlobalRateLimitEvent.Reset(); + this.GlobalRateLimitEvent?.Reset(); if (this._bucketCleanerTokenSource?.IsCancellationRequested == false) { @@ -703,9 +700,9 @@ public void Dispose() } catch { } - this.RoutesToHashes.Clear(); - this.HashesToBuckets.Clear(); - this.RequestQueue.Clear(); + this.RoutesToHashes?.Clear(); + this.HashesToBuckets?.Clear(); + this.RequestQueue?.Clear(); } } } From c4c796e0b5e57eb67eaa427c85da0923e4efa196 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Tue, 13 Jun 2023 11:35:44 +0200 Subject: [PATCH 09/21] update version to 4.4.2 --- DSharpPlus.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 9315078d9e..deaf2977ca 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.1 + 4.4.2 1591 9.0 True From 4a89159612d64a3d845da4a553d595f89ec5c983 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Thu, 9 Nov 2023 22:29:57 +0100 Subject: [PATCH 10/21] fix a null reference fixed in v5 by #1549 Co-authored by: Plerx <50530305+Plerx2493@users.noreply.github.com> --- DSharpPlus.SlashCommands/SlashCommandsExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus.SlashCommands/SlashCommandsExtension.cs b/DSharpPlus.SlashCommands/SlashCommandsExtension.cs index 93e81035fd..31fc75fa77 100644 --- a/DSharpPlus.SlashCommands/SlashCommandsExtension.cs +++ b/DSharpPlus.SlashCommands/SlashCommandsExtension.cs @@ -50,7 +50,7 @@ public sealed class SlashCommandsExtension : BaseExtension internal SlashCommandsExtension(SlashCommandsConfiguration configuration) { - this._configuration = configuration; + this._configuration = configuration ?? new SlashCommandsConfiguration(); } /// From 1c21c940db660dead0825c79ee4ae4a86b1fb20a Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Thu, 9 Nov 2023 22:35:30 +0100 Subject: [PATCH 11/21] update versions actually --- DSharpPlus.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index deaf2977ca..ebf97c20c2 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.2 + 4.4.3 1591 9.0 True From 81063d1280feac93aee8ca5313705b29e7c1a874 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Sun, 17 Dec 2023 14:30:10 +0100 Subject: [PATCH 12/21] fix an issue where ratelimits wouldn't apply correctly using the sharded client every shard would maintain its own rest client with its own ratelimit tracker, which allowed preventable 429s if a ratelimit was known, but not to the shard making the request Co-authored-by: Plerx <50530305+Plerx2493@users.noreply.github.com> --- DSharpPlus.targets | 2 +- DSharpPlus/Clients/BaseDiscordClient.cs | 4 ++-- DSharpPlus/Clients/DiscordClient.cs | 22 ++++++++++++++++++++++ DSharpPlus/Clients/DiscordShardedClient.cs | 1 + DSharpPlus/Net/Rest/DiscordApiClient.cs | 4 ++-- DSharpPlus/Net/Rest/RestClient.cs | 7 +++---- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index ebf97c20c2..1ec83f407c 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.3 + 4.4.4 1591 9.0 True diff --git a/DSharpPlus/Clients/BaseDiscordClient.cs b/DSharpPlus/Clients/BaseDiscordClient.cs index 2dcce3017b..3b3fa1c5e0 100644 --- a/DSharpPlus/Clients/BaseDiscordClient.cs +++ b/DSharpPlus/Clients/BaseDiscordClient.cs @@ -89,7 +89,7 @@ public IReadOnlyDictionary VoiceRegions /// Initializes this Discord API client. /// /// Configuration for this client. - protected BaseDiscordClient(DiscordConfiguration config) + internal BaseDiscordClient(DiscordConfiguration config, RestClient restClient = null) { this.Configuration = new DiscordConfiguration(config); @@ -100,7 +100,7 @@ protected BaseDiscordClient(DiscordConfiguration config) } this.Logger = this.Configuration.LoggerFactory.CreateLogger(); - this.ApiClient = new DiscordApiClient(this); + this.ApiClient = new DiscordApiClient(this, restClient); this.UserCache = new ConcurrentDictionary(); this.InternalVoiceRegions = new ConcurrentDictionary(); this._voice_regions_lazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions)); diff --git a/DSharpPlus/Clients/DiscordClient.cs b/DSharpPlus/Clients/DiscordClient.cs index 38ff52f0ba..b74c282964 100644 --- a/DSharpPlus/Clients/DiscordClient.cs +++ b/DSharpPlus/Clients/DiscordClient.cs @@ -150,6 +150,28 @@ public DiscordClient(DiscordConfiguration config) this.PrivateChannels = new ReadOnlyConcurrentDictionary(this._privateChannels); } + /// + /// This constructor is used when constructing a sharded client to use a shared rest client. + /// + /// Specifies configuration parameters. + /// Restclient which will be used for the underlying ApiClients + internal DiscordClient(DiscordConfiguration config, RestClient restClient) + : base(config, restClient) + { + if (this.Configuration.MessageCacheSize > 0) + { + var intents = this.Configuration.Intents; + this.MessageCache = intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages) + ? new RingBuffer(this.Configuration.MessageCacheSize) + : null; + } + + this.InternalSetup(); + + this.Guilds = new ReadOnlyConcurrentDictionary(this._guilds); + this.PrivateChannels = new ReadOnlyConcurrentDictionary(this._privateChannels); + } + internal void InternalSetup() { this._clientErrored = new AsyncEvent("CLIENT_ERRORED", this.Goof); diff --git a/DSharpPlus/Clients/DiscordShardedClient.cs b/DSharpPlus/Clients/DiscordShardedClient.cs index 2545110e99..48279f6756 100644 --- a/DSharpPlus/Clients/DiscordShardedClient.cs +++ b/DSharpPlus/Clients/DiscordShardedClient.cs @@ -244,6 +244,7 @@ public async Task InitializeShardsAsync() this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false); var shardc = this.Configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; var lf = new ShardedLoggerFactory(this.Logger); + var rc = new RestClient(this.Configuration, lf.CreateLogger("Rest")); for (var i = 0; i < shardc; i++) { var cfg = new DiscordConfiguration(this.Configuration) diff --git a/DSharpPlus/Net/Rest/DiscordApiClient.cs b/DSharpPlus/Net/Rest/DiscordApiClient.cs index 507eddbe9b..b0905398c2 100644 --- a/DSharpPlus/Net/Rest/DiscordApiClient.cs +++ b/DSharpPlus/Net/Rest/DiscordApiClient.cs @@ -46,10 +46,10 @@ public sealed class DiscordApiClient internal BaseDiscordClient _discord { get; } internal RestClient _rest { get; } - internal DiscordApiClient(BaseDiscordClient client) + internal DiscordApiClient(BaseDiscordClient client, RestClient rest = null) { this._discord = client; - this._rest = new RestClient(client); + this._rest = rest ?? new RestClient(client.Configuration, client.Logger); } internal DiscordApiClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRateLimit, ILogger logger) // This is for meta-clients, such as the webhook client diff --git a/DSharpPlus/Net/Rest/RestClient.cs b/DSharpPlus/Net/Rest/RestClient.cs index 155de7f5ed..6f495dc76b 100644 --- a/DSharpPlus/Net/Rest/RestClient.cs +++ b/DSharpPlus/Net/Rest/RestClient.cs @@ -60,11 +60,10 @@ internal sealed class RestClient : IDisposable private Task _cleanerTask; private volatile bool _disposed; - internal RestClient(BaseDiscordClient client) - : this(client.Configuration.Proxy, client.Configuration.HttpTimeout, client.Configuration.UseRelativeRatelimit, client.Logger) + internal RestClient(DiscordConfiguration config, ILogger logger) + : this(config.Proxy, config.HttpTimeout, config.UseRelativeRatelimit, logger) { - this.Discord = client; - this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(client)); + this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(config)); } internal RestClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRatelimit, From c0c64b123c31e4195220535f3d314191c61e75ce Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Sun, 17 Dec 2023 14:34:48 +0100 Subject: [PATCH 13/21] communicate the shared rest client to shards correctly Co-authored-by: Plerx <50530305+Plerx2493@users.noreply.github.com> --- DSharpPlus/Clients/DiscordShardedClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus/Clients/DiscordShardedClient.cs b/DSharpPlus/Clients/DiscordShardedClient.cs index 48279f6756..fa53b2ede4 100644 --- a/DSharpPlus/Clients/DiscordShardedClient.cs +++ b/DSharpPlus/Clients/DiscordShardedClient.cs @@ -254,7 +254,7 @@ public async Task InitializeShardsAsync() LoggerFactory = lf }; - var client = new DiscordClient(cfg); + var client = new DiscordClient(cfg, rc); if (!this._shards.TryAdd(i, client)) throw new InvalidOperationException("Could not initialize shards."); } From f9849702774f678712c61656c5d4555f1046dc78 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Sun, 17 Dec 2023 14:39:00 +0100 Subject: [PATCH 14/21] update version --- DSharpPlus.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 1ec83f407c..6902ba2fee 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.4 + 4.4.5 1591 9.0 True From 3a90ac8625573943ffe930a328de81180ad193dd Mon Sep 17 00:00:00 2001 From: Plerx <50530305+Plerx2493@users.noreply.github.com> Date: Sat, 23 Dec 2023 18:13:27 +0100 Subject: [PATCH 15/21] 4.4.6 - DiscordShardedClient logger fix (#1709) * Remove restriction from ShardedLoggerFactory * update version * free the DefaultLoggerFactory --- DSharpPlus.targets | 2 +- DSharpPlus/Clients/DiscordShardedClient.cs | 26 +++++++++++----------- DSharpPlus/Logging/DefaultLoggerFactory.cs | 13 ++++------- DSharpPlus/Logging/ShardedLoggerFactory.cs | 16 +++++-------- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 6902ba2fee..9a1f1dd529 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.5 + 4.4.6 1591 9.0 True diff --git a/DSharpPlus/Clients/DiscordShardedClient.cs b/DSharpPlus/Clients/DiscordShardedClient.cs index fa53b2ede4..3091ee5734 100644 --- a/DSharpPlus/Clients/DiscordShardedClient.cs +++ b/DSharpPlus/Clients/DiscordShardedClient.cs @@ -241,25 +241,25 @@ public async Task InitializeShardsAsync() if (this._shards.Count != 0) return this._shards.Count; - this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false); - var shardc = this.Configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; - var lf = new ShardedLoggerFactory(this.Logger); - var rc = new RestClient(this.Configuration, lf.CreateLogger("Rest")); - for (var i = 0; i < shardc; i++) + this.GatewayInfo = await this.GetGatewayInfoAsync(); + int shardCount = this.Configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; + ShardedLoggerFactory loggerFactory = new ShardedLoggerFactory(this.Configuration.LoggerFactory); + RestClient restClient = new RestClient(this.Configuration, loggerFactory.CreateLogger("Rest")); + for (int i = 0; i < shardCount; i++) + { + DiscordConfiguration cfg = new DiscordConfiguration(this.Configuration) { - var cfg = new DiscordConfiguration(this.Configuration) - { - ShardId = i, - ShardCount = shardc, - LoggerFactory = lf - }; + ShardId = i, + ShardCount = shardCount, + LoggerFactory = loggerFactory + }; - var client = new DiscordClient(cfg, rc); + var client = new DiscordClient(cfg, restClient); if (!this._shards.TryAdd(i, client)) throw new InvalidOperationException("Could not initialize shards."); } - return shardc; + return shardCount; } #endregion diff --git a/DSharpPlus/Logging/DefaultLoggerFactory.cs b/DSharpPlus/Logging/DefaultLoggerFactory.cs index 1303a625c9..a498ca66e5 100644 --- a/DSharpPlus/Logging/DefaultLoggerFactory.cs +++ b/DSharpPlus/Logging/DefaultLoggerFactory.cs @@ -34,15 +34,10 @@ internal class DefaultLoggerFactory : ILoggerFactory public void AddProvider(ILoggerProvider provider) => this.Providers.Add(provider); - public ILogger CreateLogger(string categoryName) - { - if (this._isDisposed) - throw new InvalidOperationException("This logger factory is already disposed."); - - return categoryName != typeof(BaseDiscordClient).FullName && categoryName != typeof(DiscordWebhookClient).FullName - ? throw new ArgumentException($"This factory can only provide instances of loggers for {typeof(BaseDiscordClient).FullName} or {typeof(DiscordWebhookClient).FullName}.", nameof(categoryName)) - : new CompositeDefaultLogger(this.Providers); - } + public ILogger CreateLogger(string categoryName) => + this._isDisposed + ? throw new InvalidOperationException("This logger factory is already disposed.") + : new CompositeDefaultLogger(this.Providers); public void Dispose() { diff --git a/DSharpPlus/Logging/ShardedLoggerFactory.cs b/DSharpPlus/Logging/ShardedLoggerFactory.cs index aa8d974245..e865523a00 100644 --- a/DSharpPlus/Logging/ShardedLoggerFactory.cs +++ b/DSharpPlus/Logging/ShardedLoggerFactory.cs @@ -23,26 +23,20 @@ using System; using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; namespace DSharpPlus { internal class ShardedLoggerFactory : ILoggerFactory { - private ILogger Logger { get; } + private ConcurrentDictionary Loggers { get; } = new(); + private ILoggerFactory Factory { get; } - public ShardedLoggerFactory(ILogger instance) - { - this.Logger = instance; - } + public ShardedLoggerFactory(ILoggerFactory factory) => this.Factory = factory; public void AddProvider(ILoggerProvider provider) => throw new InvalidOperationException("This is a passthrough logger container, it cannot register new providers."); - public ILogger CreateLogger(string categoryName) - { - return categoryName != typeof(BaseDiscordClient).FullName - ? throw new ArgumentException($"This factory can only provide instances of loggers for {typeof(BaseDiscordClient).FullName}.", nameof(categoryName)) - : this.Logger; - } + public ILogger CreateLogger(string categoryName) => this.Loggers.GetOrAdd(categoryName, this.Factory.CreateLogger(categoryName)); public void Dispose() { } From dd8cc75d9d6bc416b845f2f73e7787b274bbec91 Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Thu, 27 Jun 2024 15:26:14 +0200 Subject: [PATCH 16/21] relax Permissions to permit over-length permissions --- DSharpPlus.targets | 2 +- DSharpPlus/Entities/Channel/DiscordChannel.cs | 1 + .../Channel/Overwrite/DiscordOverwrite.cs | 3 + .../Overwrite/DiscordOverwriteBuilder.cs | 3 + DSharpPlus/Entities/Guild/DiscordGuild.cs | 1 + DSharpPlus/Entities/Guild/DiscordRole.cs | 2 + .../Application/DiscordApplicationCommand.cs | 2 + .../Rest/RestApplicationCommandPayloads.cs | 3 +- .../Abstractions/Rest/RestChannelPayloads.cs | 3 + .../Abstractions/Rest/RestGuildPayloads.cs | 2 + .../Net/Abstractions/Rest/RestUserPayloads.cs | 2 + DSharpPlus/Net/Serialization/DiscordJson.cs | 2 +- .../DiscordPermissionsJsonConverter.cs | 45 +++ v4-permission-relaxation.diff | 265 ++++++++++++++++++ 14 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs create mode 100644 v4-permission-relaxation.diff diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 9a1f1dd529..1f8ddcf1ec 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.6 + 4.4.7 1591 9.0 True diff --git a/DSharpPlus/Entities/Channel/DiscordChannel.cs b/DSharpPlus/Entities/Channel/DiscordChannel.cs index 21d136d820..0fb8f28a9a 100644 --- a/DSharpPlus/Entities/Channel/DiscordChannel.cs +++ b/DSharpPlus/Entities/Channel/DiscordChannel.cs @@ -236,6 +236,7 @@ public DiscordVoiceRegion RtcRegion /// Only sent on the resolved channels of interaction responses for application commands. /// [JsonProperty("permissions")] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions? UserPermissions { get; internal set; } internal DiscordChannel() diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs index 134d6dbc6c..a890b5e45b 100644 --- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs +++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs @@ -23,6 +23,7 @@ using System; using System.Threading.Tasks; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Entities @@ -42,12 +43,14 @@ public class DiscordOverwrite : SnowflakeObject /// Gets the allowed permission set. /// [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Allowed { get; internal set; } /// /// Gets the denied permission set. /// [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Denied { get; internal set; } [JsonIgnore] diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs index 22bd400c63..5c0a2974ea 100644 --- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs +++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs @@ -23,6 +23,7 @@ using System; using System.Threading.Tasks; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Entities @@ -158,9 +159,11 @@ internal DiscordRestOverwrite Build() internal struct DiscordRestOverwrite { [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] internal Permissions Allow { get; set; } [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] internal Permissions Deny { get; set; } [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] diff --git a/DSharpPlus/Entities/Guild/DiscordGuild.cs b/DSharpPlus/Entities/Guild/DiscordGuild.cs index aeeaa3e0b4..2b8f57ac41 100644 --- a/DSharpPlus/Entities/Guild/DiscordGuild.cs +++ b/DSharpPlus/Entities/Guild/DiscordGuild.cs @@ -109,6 +109,7 @@ public string DiscoverySplashUrl /// Gets permissions for the user in the guild (does not include channel overrides) /// [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions? Permissions { get; set; } /// diff --git a/DSharpPlus/Entities/Guild/DiscordRole.cs b/DSharpPlus/Entities/Guild/DiscordRole.cs index 9ae6a145db..880c278011 100644 --- a/DSharpPlus/Entities/Guild/DiscordRole.cs +++ b/DSharpPlus/Entities/Guild/DiscordRole.cs @@ -27,6 +27,7 @@ using System.Threading.Tasks; using DSharpPlus.Net.Abstractions; using DSharpPlus.Net.Models; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Entities @@ -87,6 +88,7 @@ public DiscordColor Color /// Gets the permissions set for this role. /// [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Permissions { get; internal set; } /// diff --git a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs index 776f34af05..cc1110a6ff 100644 --- a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs +++ b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs @@ -26,6 +26,7 @@ using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Entities @@ -81,6 +82,7 @@ public sealed class DiscordApplicationCommand : SnowflakeObject, IEquatable [JsonProperty("default_member_permissions")] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions? DefaultMemberPermissions { get; internal set; } diff --git a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs index 4c7607c67c..99d192aacb 100644 --- a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs +++ b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs @@ -23,6 +23,7 @@ using System.Collections.Generic; using DSharpPlus.Entities; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Net.Abstractions @@ -54,8 +55,8 @@ internal class RestApplicationCommandCreatePayload public bool? AllowDMUsage { get; set; } [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions? DefaultMemberPermissions { get; set; } - } internal class RestApplicationCommandEditPayload diff --git a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs index 243d3b50b9..5a0b802ec3 100644 --- a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs +++ b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using DSharpPlus.Entities; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Net.Abstractions @@ -287,9 +288,11 @@ internal sealed class RestChannelInviteCreatePayload internal sealed class RestChannelPermissionEditPayload { [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Allow { get; set; } [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Deny { get; set; } [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] diff --git a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs index 77c107bcb8..e9d1ae4dae 100644 --- a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs +++ b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using DSharpPlus.Entities; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Net.Abstractions @@ -261,6 +262,7 @@ internal sealed class RestGuildRolePayload public string Name { get; set; } [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions? Permissions { get; set; } [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] diff --git a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs index 1a1bbb3216..10e2ac2bc2 100644 --- a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs +++ b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs @@ -22,6 +22,7 @@ // SOFTWARE. using System.Collections.Generic; +using DSharpPlus.Net.Serialization; using Newtonsoft.Json; namespace DSharpPlus.Net.Abstractions @@ -71,6 +72,7 @@ internal sealed class RestUserGuild public bool IsOwner { get; set; } [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(DiscordPermissionsJsonConverter))] public Permissions Permissions { get; set; } } diff --git a/DSharpPlus/Net/Serialization/DiscordJson.cs b/DSharpPlus/Net/Serialization/DiscordJson.cs index 67db38ad0b..e09b405e11 100644 --- a/DSharpPlus/Net/Serialization/DiscordJson.cs +++ b/DSharpPlus/Net/Serialization/DiscordJson.cs @@ -37,7 +37,7 @@ public static class DiscordJson { ContractResolver = new OptionalJsonContractResolver(), DateParseHandling = DateParseHandling.None, - Converters = new[] { new ISO8601DateTimeOffsetJsonConverter() } + Converters = new JsonConverter[] { new ISO8601DateTimeOffsetJsonConverter(), new DiscordPermissionsJsonConverter() } }); /// Serializes the specified object to a JSON string. diff --git a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs new file mode 100644 index 0000000000..73d73edacf --- /dev/null +++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs @@ -0,0 +1,45 @@ +// This file is part of the DSharpPlus project. +// +// Copyright (c) 2015 Mike Santiago +// Copyright (c) 2016-2023 DSharpPlus Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// 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. + +using System; +using System.Globalization; +using System.Numerics; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DSharpPlus.Net.Serialization +{ + internal class DiscordPermissionsJsonConverter : JsonConverter + { + public override Permissions ReadJson(JsonReader reader, Type objectType, Permissions existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JToken.Load(reader); + var value = token.ToObject(); + + return (Permissions)(ulong)(value & ulong.MaxValue); + } + + public override void WriteJson(JsonWriter writer, Permissions value, JsonSerializer serializer) + => writer.WriteValue(((ulong)value).ToString(CultureInfo.InvariantCulture)); + } +} diff --git a/v4-permission-relaxation.diff b/v4-permission-relaxation.diff new file mode 100644 index 0000000000..78812e1133 --- /dev/null +++ b/v4-permission-relaxation.diff @@ -0,0 +1,265 @@ +diff --git a/DSharpPlus/Entities/Channel/DiscordChannel.cs b/DSharpPlus/Entities/Channel/DiscordChannel.cs +index 21d136d8..0fb8f28a 100644 +--- a/DSharpPlus/Entities/Channel/DiscordChannel.cs ++++ b/DSharpPlus/Entities/Channel/DiscordChannel.cs +@@ -236,6 +236,7 @@ namespace DSharpPlus.Entities + /// Only sent on the resolved channels of interaction responses for application commands. + /// + [JsonProperty("permissions")] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions? UserPermissions { get; internal set; } + + internal DiscordChannel() +diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs +index 134d6dbc..a890b5e4 100644 +--- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs ++++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs +@@ -23,6 +23,7 @@ + + using System; + using System.Threading.Tasks; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Entities +@@ -42,12 +43,14 @@ namespace DSharpPlus.Entities + /// Gets the allowed permission set. + /// + [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Allowed { get; internal set; } + + /// + /// Gets the denied permission set. + /// + [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Denied { get; internal set; } + + [JsonIgnore] +diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs +index 22bd400c..5c0a2974 100644 +--- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs ++++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs +@@ -23,6 +23,7 @@ + + using System; + using System.Threading.Tasks; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Entities +@@ -158,9 +159,11 @@ namespace DSharpPlus.Entities + internal struct DiscordRestOverwrite + { + [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + internal Permissions Allow { get; set; } + + [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + internal Permissions Deny { get; set; } + + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] +diff --git a/DSharpPlus/Entities/Guild/DiscordGuild.cs b/DSharpPlus/Entities/Guild/DiscordGuild.cs +index aeeaa3e0..2b8f57ac 100644 +--- a/DSharpPlus/Entities/Guild/DiscordGuild.cs ++++ b/DSharpPlus/Entities/Guild/DiscordGuild.cs +@@ -109,6 +109,7 @@ namespace DSharpPlus.Entities + /// Gets permissions for the user in the guild (does not include channel overrides) + /// + [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions? Permissions { get; set; } + + /// +diff --git a/DSharpPlus/Entities/Guild/DiscordRole.cs b/DSharpPlus/Entities/Guild/DiscordRole.cs +index 9ae6a145..880c2780 100644 +--- a/DSharpPlus/Entities/Guild/DiscordRole.cs ++++ b/DSharpPlus/Entities/Guild/DiscordRole.cs +@@ -27,6 +27,7 @@ using System.Linq; + using System.Threading.Tasks; + using DSharpPlus.Net.Abstractions; + using DSharpPlus.Net.Models; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Entities +@@ -87,6 +88,7 @@ namespace DSharpPlus.Entities + /// Gets the permissions set for this role. + /// + [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Permissions { get; internal set; } + + /// +diff --git a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs +index 776f34af..cc1110a6 100644 +--- a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs ++++ b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs +@@ -26,6 +26,7 @@ using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Globalization; + using System.Linq; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Entities +@@ -81,6 +82,7 @@ namespace DSharpPlus.Entities + /// What permissions this command requires to be invoked. + /// + [JsonProperty("default_member_permissions")] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions? DefaultMemberPermissions { get; internal set; } + + +diff --git a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs +index 4c7607c6..99d192aa 100644 +--- a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs ++++ b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs +@@ -23,6 +23,7 @@ + + using System.Collections.Generic; + using DSharpPlus.Entities; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Net.Abstractions +@@ -54,8 +55,8 @@ namespace DSharpPlus.Net.Abstractions + public bool? AllowDMUsage { get; set; } + + [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions? DefaultMemberPermissions { get; set; } +- + } + + internal class RestApplicationCommandEditPayload +diff --git a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs +index 243d3b50..5a0b802e 100644 +--- a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs ++++ b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs +@@ -24,6 +24,7 @@ + using System; + using System.Collections.Generic; + using DSharpPlus.Entities; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Net.Abstractions +@@ -287,9 +288,11 @@ namespace DSharpPlus.Net.Abstractions + internal sealed class RestChannelPermissionEditPayload + { + [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Allow { get; set; } + + [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Deny { get; set; } + + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] +diff --git a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs +index 77c107bc..e9d1ae4d 100644 +--- a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs ++++ b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs +@@ -24,6 +24,7 @@ + using System; + using System.Collections.Generic; + using DSharpPlus.Entities; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Net.Abstractions +@@ -261,6 +262,7 @@ namespace DSharpPlus.Net.Abstractions + public string Name { get; set; } + + [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions? Permissions { get; set; } + + [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] +diff --git a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs +index 1a1bbb32..10e2ac2b 100644 +--- a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs ++++ b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs +@@ -22,6 +22,7 @@ + // SOFTWARE. + + using System.Collections.Generic; ++using DSharpPlus.Net.Serialization; + using Newtonsoft.Json; + + namespace DSharpPlus.Net.Abstractions +@@ -71,6 +72,7 @@ namespace DSharpPlus.Net.Abstractions + public bool IsOwner { get; set; } + + [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] ++ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] + public Permissions Permissions { get; set; } + } + +diff --git a/DSharpPlus/Net/Serialization/DiscordJson.cs b/DSharpPlus/Net/Serialization/DiscordJson.cs +index 67db38ad..e09b405e 100644 +--- a/DSharpPlus/Net/Serialization/DiscordJson.cs ++++ b/DSharpPlus/Net/Serialization/DiscordJson.cs +@@ -37,7 +37,7 @@ namespace DSharpPlus.Net.Serialization + { + ContractResolver = new OptionalJsonContractResolver(), + DateParseHandling = DateParseHandling.None, +- Converters = new[] { new ISO8601DateTimeOffsetJsonConverter() } ++ Converters = new JsonConverter[] { new ISO8601DateTimeOffsetJsonConverter(), new DiscordPermissionsJsonConverter() } + }); + + /// Serializes the specified object to a JSON string. +diff --git a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs +new file mode 100644 +index 00000000..73d73eda +--- /dev/null ++++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs +@@ -0,0 +1,45 @@ ++// This file is part of the DSharpPlus project. ++// ++// Copyright (c) 2015 Mike Santiago ++// Copyright (c) 2016-2023 DSharpPlus Contributors ++// ++// Permission is hereby granted, free of charge, to any person obtaining a copy ++// of this software and associated documentation files (the "Software"), to deal ++// 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. ++ ++using System; ++using System.Globalization; ++using System.Numerics; ++using Newtonsoft.Json; ++using Newtonsoft.Json.Linq; ++ ++namespace DSharpPlus.Net.Serialization ++{ ++ internal class DiscordPermissionsJsonConverter : JsonConverter ++ { ++ public override Permissions ReadJson(JsonReader reader, Type objectType, Permissions existingValue, bool hasExistingValue, JsonSerializer serializer) ++ { ++ var token = JToken.Load(reader); ++ var value = token.ToObject(); ++ ++ return (Permissions)(ulong)(value & ulong.MaxValue); ++ } ++ ++ public override void WriteJson(JsonWriter writer, Permissions value, JsonSerializer serializer) ++ => writer.WriteValue(((ulong)value).ToString(CultureInfo.InvariantCulture)); ++ } ++} From eb8a1b22171120f90d6c2ab2c6743ebeee6cdebe Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Fri, 28 Jun 2024 21:35:54 +0200 Subject: [PATCH 17/21] handle null permissions --- DSharpPlus.targets | 2 +- .../DiscordPermissionsJsonConverter.cs | 6 + v4-permission-relaxation.diff | 265 ------------------ 3 files changed, 7 insertions(+), 266 deletions(-) delete mode 100644 v4-permission-relaxation.diff diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 1f8ddcf1ec..b8b26d6f7f 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.7 + 4.4.8 1591 9.0 True diff --git a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs index 73d73edacf..9291ba0be0 100644 --- a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs +++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs @@ -34,6 +34,12 @@ internal class DiscordPermissionsJsonConverter : JsonConverter public override Permissions ReadJson(JsonReader reader, Type objectType, Permissions existingValue, bool hasExistingValue, JsonSerializer serializer) { var token = JToken.Load(reader); + + if (token != null || token.Type == JTokenType.Null) + { + return Permissions.None; + } + var value = token.ToObject(); return (Permissions)(ulong)(value & ulong.MaxValue); diff --git a/v4-permission-relaxation.diff b/v4-permission-relaxation.diff deleted file mode 100644 index 78812e1133..0000000000 --- a/v4-permission-relaxation.diff +++ /dev/null @@ -1,265 +0,0 @@ -diff --git a/DSharpPlus/Entities/Channel/DiscordChannel.cs b/DSharpPlus/Entities/Channel/DiscordChannel.cs -index 21d136d8..0fb8f28a 100644 ---- a/DSharpPlus/Entities/Channel/DiscordChannel.cs -+++ b/DSharpPlus/Entities/Channel/DiscordChannel.cs -@@ -236,6 +236,7 @@ namespace DSharpPlus.Entities - /// Only sent on the resolved channels of interaction responses for application commands. - /// - [JsonProperty("permissions")] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions? UserPermissions { get; internal set; } - - internal DiscordChannel() -diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs -index 134d6dbc..a890b5e4 100644 ---- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs -+++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwrite.cs -@@ -23,6 +23,7 @@ - - using System; - using System.Threading.Tasks; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Entities -@@ -42,12 +43,14 @@ namespace DSharpPlus.Entities - /// Gets the allowed permission set. - /// - [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Allowed { get; internal set; } - - /// - /// Gets the denied permission set. - /// - [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Denied { get; internal set; } - - [JsonIgnore] -diff --git a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs -index 22bd400c..5c0a2974 100644 ---- a/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs -+++ b/DSharpPlus/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs -@@ -23,6 +23,7 @@ - - using System; - using System.Threading.Tasks; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Entities -@@ -158,9 +159,11 @@ namespace DSharpPlus.Entities - internal struct DiscordRestOverwrite - { - [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - internal Permissions Allow { get; set; } - - [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - internal Permissions Deny { get; set; } - - [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] -diff --git a/DSharpPlus/Entities/Guild/DiscordGuild.cs b/DSharpPlus/Entities/Guild/DiscordGuild.cs -index aeeaa3e0..2b8f57ac 100644 ---- a/DSharpPlus/Entities/Guild/DiscordGuild.cs -+++ b/DSharpPlus/Entities/Guild/DiscordGuild.cs -@@ -109,6 +109,7 @@ namespace DSharpPlus.Entities - /// Gets permissions for the user in the guild (does not include channel overrides) - /// - [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions? Permissions { get; set; } - - /// -diff --git a/DSharpPlus/Entities/Guild/DiscordRole.cs b/DSharpPlus/Entities/Guild/DiscordRole.cs -index 9ae6a145..880c2780 100644 ---- a/DSharpPlus/Entities/Guild/DiscordRole.cs -+++ b/DSharpPlus/Entities/Guild/DiscordRole.cs -@@ -27,6 +27,7 @@ using System.Linq; - using System.Threading.Tasks; - using DSharpPlus.Net.Abstractions; - using DSharpPlus.Net.Models; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Entities -@@ -87,6 +88,7 @@ namespace DSharpPlus.Entities - /// Gets the permissions set for this role. - /// - [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Permissions { get; internal set; } - - /// -diff --git a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs -index 776f34af..cc1110a6 100644 ---- a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs -+++ b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs -@@ -26,6 +26,7 @@ using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Globalization; - using System.Linq; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Entities -@@ -81,6 +82,7 @@ namespace DSharpPlus.Entities - /// What permissions this command requires to be invoked. - /// - [JsonProperty("default_member_permissions")] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions? DefaultMemberPermissions { get; internal set; } - - -diff --git a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs -index 4c7607c6..99d192aa 100644 ---- a/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs -+++ b/DSharpPlus/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs -@@ -23,6 +23,7 @@ - - using System.Collections.Generic; - using DSharpPlus.Entities; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Net.Abstractions -@@ -54,8 +55,8 @@ namespace DSharpPlus.Net.Abstractions - public bool? AllowDMUsage { get; set; } - - [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions? DefaultMemberPermissions { get; set; } -- - } - - internal class RestApplicationCommandEditPayload -diff --git a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs -index 243d3b50..5a0b802e 100644 ---- a/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs -+++ b/DSharpPlus/Net/Abstractions/Rest/RestChannelPayloads.cs -@@ -24,6 +24,7 @@ - using System; - using System.Collections.Generic; - using DSharpPlus.Entities; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Net.Abstractions -@@ -287,9 +288,11 @@ namespace DSharpPlus.Net.Abstractions - internal sealed class RestChannelPermissionEditPayload - { - [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Allow { get; set; } - - [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Deny { get; set; } - - [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] -diff --git a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs -index 77c107bc..e9d1ae4d 100644 ---- a/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs -+++ b/DSharpPlus/Net/Abstractions/Rest/RestGuildPayloads.cs -@@ -24,6 +24,7 @@ - using System; - using System.Collections.Generic; - using DSharpPlus.Entities; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Net.Abstractions -@@ -261,6 +262,7 @@ namespace DSharpPlus.Net.Abstractions - public string Name { get; set; } - - [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions? Permissions { get; set; } - - [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] -diff --git a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs -index 1a1bbb32..10e2ac2b 100644 ---- a/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs -+++ b/DSharpPlus/Net/Abstractions/Rest/RestUserPayloads.cs -@@ -22,6 +22,7 @@ - // SOFTWARE. - - using System.Collections.Generic; -+using DSharpPlus.Net.Serialization; - using Newtonsoft.Json; - - namespace DSharpPlus.Net.Abstractions -@@ -71,6 +72,7 @@ namespace DSharpPlus.Net.Abstractions - public bool IsOwner { get; set; } - - [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] -+ [JsonConverter(typeof(DiscordPermissionsJsonConverter))] - public Permissions Permissions { get; set; } - } - -diff --git a/DSharpPlus/Net/Serialization/DiscordJson.cs b/DSharpPlus/Net/Serialization/DiscordJson.cs -index 67db38ad..e09b405e 100644 ---- a/DSharpPlus/Net/Serialization/DiscordJson.cs -+++ b/DSharpPlus/Net/Serialization/DiscordJson.cs -@@ -37,7 +37,7 @@ namespace DSharpPlus.Net.Serialization - { - ContractResolver = new OptionalJsonContractResolver(), - DateParseHandling = DateParseHandling.None, -- Converters = new[] { new ISO8601DateTimeOffsetJsonConverter() } -+ Converters = new JsonConverter[] { new ISO8601DateTimeOffsetJsonConverter(), new DiscordPermissionsJsonConverter() } - }); - - /// Serializes the specified object to a JSON string. -diff --git a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs -new file mode 100644 -index 00000000..73d73eda ---- /dev/null -+++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs -@@ -0,0 +1,45 @@ -+// This file is part of the DSharpPlus project. -+// -+// Copyright (c) 2015 Mike Santiago -+// Copyright (c) 2016-2023 DSharpPlus Contributors -+// -+// Permission is hereby granted, free of charge, to any person obtaining a copy -+// of this software and associated documentation files (the "Software"), to deal -+// 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. -+ -+using System; -+using System.Globalization; -+using System.Numerics; -+using Newtonsoft.Json; -+using Newtonsoft.Json.Linq; -+ -+namespace DSharpPlus.Net.Serialization -+{ -+ internal class DiscordPermissionsJsonConverter : JsonConverter -+ { -+ public override Permissions ReadJson(JsonReader reader, Type objectType, Permissions existingValue, bool hasExistingValue, JsonSerializer serializer) -+ { -+ var token = JToken.Load(reader); -+ var value = token.ToObject(); -+ -+ return (Permissions)(ulong)(value & ulong.MaxValue); -+ } -+ -+ public override void WriteJson(JsonWriter writer, Permissions value, JsonSerializer serializer) -+ => writer.WriteValue(((ulong)value).ToString(CultureInfo.InvariantCulture)); -+ } -+} From 9833164c15781b72f29619d7fc601857861ae74c Mon Sep 17 00:00:00 2001 From: akiraveliara Date: Sun, 30 Jun 2024 18:32:00 +0200 Subject: [PATCH 18/21] fix #1979 --- DSharpPlus.targets | 2 +- DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index b8b26d6f7f..0e23620ff3 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.8 + 4.4.9 1591 9.0 True diff --git a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs index 9291ba0be0..963f1f174c 100644 --- a/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs +++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs @@ -35,7 +35,7 @@ public override Permissions ReadJson(JsonReader reader, Type objectType, Permiss { var token = JToken.Load(reader); - if (token != null || token.Type == JTokenType.Null) + if (token == null || token.Type == JTokenType.Null) { return Permissions.None; } From 781fa9671369ebbb1a70229a9f8cdcccf08b5c6e Mon Sep 17 00:00:00 2001 From: Livia-Valeriya Silvercrown <61018569+akiraveliara@users.noreply.github.com> Date: Sun, 1 Sep 2024 14:39:13 +0200 Subject: [PATCH 19/21] deal with top-level components that aren't action rows (#2083) * orrore, orrore, orrore! * fix interactivity --- .../InteractivityExtension.cs | 26 ++++++++------- DSharpPlus.targets | 2 +- .../Channel/Message/DiscordMessage.cs | 32 ++++++++++++++++++- .../Channel/Message/DiscordMessageBuilder.cs | 5 ++- .../Interaction/DiscordInteractionData.cs | 4 +-- .../Interaction/ModalSubmitEventArgs.cs | 2 +- 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/DSharpPlus.Interactivity/InteractivityExtension.cs b/DSharpPlus.Interactivity/InteractivityExtension.cs index 906eb109ac..edf6f9997d 100644 --- a/DSharpPlus.Interactivity/InteractivityExtension.cs +++ b/DSharpPlus.Interactivity/InteractivityExtension.cs @@ -206,7 +206,7 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) + if (!message.FilterComponents().Any()) throw new ArgumentException("Provided message does not contain any button components."); @@ -246,10 +246,12 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) + IReadOnlyList buttons = message.FilterComponents(); + + if (!buttons.Any()) throw new ArgumentException("Provided message does not contain any button components."); - var ids = message.Components.SelectMany(m => m.Components).Select(c => c.CustomId); + var ids = buttons.Select(c => c.CustomId); var result = await this @@ -289,7 +291,7 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) + if (!message.FilterComponents().Any()) throw new ArgumentException("Provided message does not contain any button components."); var result = await this @@ -330,10 +332,10 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) + if (!message.FilterComponents().Any()) throw new ArgumentException("Provided message does not contain any button components."); - if (!message.Components.SelectMany(c => c.Components).OfType().Any(c => c.CustomId == id)) + if (!message.FilterComponents().Any(c => c.CustomId == id)) throw new ArgumentException($"Provided message does not contain button with Id of '{id}'."); var result = await this @@ -367,7 +369,7 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) + if (!message.FilterComponents().Any()) throw new ArgumentException("Provided message does not contain any button components."); var result = await this @@ -403,7 +405,7 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(this.IsSelect)) + if (!message.FilterComponents().Any(this.IsSelect)) throw new ArgumentException("Provided message does not contain any select components."); @@ -442,10 +444,10 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(this.IsSelect)) + if (!message.FilterComponents().Any(this.IsSelect)) throw new ArgumentException("Provided message does not contain any select components."); - if (message.Components.SelectMany(c => c.Components).Where(this.IsSelect).All(c => c.CustomId != id)) + if (message.FilterComponents().Where(this.IsSelect).All(c => c.CustomId != id)) throw new ArgumentException($"Provided message does not contain select component with Id of '{id}'."); var result = await this @@ -495,10 +497,10 @@ public async Task> Wait if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); - if (!message.Components.SelectMany(c => c.Components).Any(this.IsSelect)) + if (!message.FilterComponents().Any(this.IsSelect)) throw new ArgumentException("Provided message does not contain any select components."); - if (message.Components.SelectMany(c => c.Components).Where(this.IsSelect).All(c => c.CustomId != id)) + if (message.FilterComponents().Where(this.IsSelect).All(c => c.CustomId != id)) throw new ArgumentException($"Provided message does not contain button with Id of '{id}'."); var result = await this diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 0e23620ff3..35fc2b2d8a 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.4.9 + 4.5.0 1591 9.0 True diff --git a/DSharpPlus/Entities/Channel/Message/DiscordMessage.cs b/DSharpPlus/Entities/Channel/Message/DiscordMessage.cs index a4ffc54c1b..182fc8e330 100644 --- a/DSharpPlus/Entities/Channel/Message/DiscordMessage.cs +++ b/DSharpPlus/Entities/Channel/Message/DiscordMessage.cs @@ -26,6 +26,7 @@ using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Newtonsoft.Json; @@ -102,7 +103,7 @@ public DiscordChannel Channel /// Gets the components this message was sent with. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] - public IReadOnlyCollection Components { get; internal set; } + public IReadOnlyCollection Components { get; internal set; } /// /// Gets the user or member that sent the message. @@ -440,6 +441,35 @@ internal void PopulateMentions() this._mentionedUsers = mentionedUsers.ToList(); } + /// + /// Searches the components on this message for an aggregate of all components of a certain type. + /// + public IReadOnlyList FilterComponents() + where T : DiscordComponent + { + List components = new(); + + foreach (DiscordComponent component in this.Components) + { + if (component is DiscordActionRowComponent actionRowComponent) + { + foreach (DiscordComponent subComponent in actionRowComponent.Components) + { + if (subComponent is T filteredComponent) + { + components.Add(filteredComponent); + } + } + } + else if (component is T filteredComponent) + { + components.Add(filteredComponent); + } + } + + return components; + } + /// /// Edits the message. /// diff --git a/DSharpPlus/Entities/Channel/Message/DiscordMessageBuilder.cs b/DSharpPlus/Entities/Channel/Message/DiscordMessageBuilder.cs index bdf388bb08..44e6a4240e 100644 --- a/DSharpPlus/Entities/Channel/Message/DiscordMessageBuilder.cs +++ b/DSharpPlus/Entities/Channel/Message/DiscordMessageBuilder.cs @@ -115,7 +115,10 @@ public DiscordMessageBuilder(DiscordMessage baseMessage) { this.IsTTS = baseMessage.IsTTS; this.ReplyId = baseMessage.ReferencedMessage?.Id; - this._components = baseMessage.Components.ToList(); // Calling ToList copies the list instead of referencing it + + // calling tolist copies the list, which is good since we inaccurately reflect the message in case of + // unknown components + this._components = baseMessage.Components.OfType().ToList(); this._content = baseMessage.Content; this._embeds = baseMessage.Embeds.ToList(); this._stickers = baseMessage.Stickers.ToList(); diff --git a/DSharpPlus/Entities/Interaction/DiscordInteractionData.cs b/DSharpPlus/Entities/Interaction/DiscordInteractionData.cs index 1767ae3fa4..c63b999c17 100644 --- a/DSharpPlus/Entities/Interaction/DiscordInteractionData.cs +++ b/DSharpPlus/Entities/Interaction/DiscordInteractionData.cs @@ -65,10 +65,10 @@ public sealed class DiscordInteractionData : SnowflakeObject /// /// Components on this interaction. Only applies to modal interactions. /// - public IReadOnlyList Components => this._components; + public IReadOnlyList Components => this._components; [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] - internal List _components; + internal List _components; /// /// The Id of the target. Applicable for context menus. diff --git a/DSharpPlus/EventArgs/Interaction/ModalSubmitEventArgs.cs b/DSharpPlus/EventArgs/Interaction/ModalSubmitEventArgs.cs index f3bd769b45..4adfbf466b 100644 --- a/DSharpPlus/EventArgs/Interaction/ModalSubmitEventArgs.cs +++ b/DSharpPlus/EventArgs/Interaction/ModalSubmitEventArgs.cs @@ -46,7 +46,7 @@ internal ModalSubmitEventArgs(DiscordInteraction interaction) var dict = new Dictionary(); foreach (var component in interaction.Data._components) - if (component.Components.First() is TextInputComponent input) + if ((component as DiscordActionRowComponent)?.Components.First() is TextInputComponent input) dict.Add(input.CustomId, input.Value); this.Values = dict; From 6ec69174d919c3a8b356f3342324aa73d8287776 Mon Sep 17 00:00:00 2001 From: Livia-Valeriya Silvercrown Date: Wed, 16 Apr 2025 13:48:37 +0200 Subject: [PATCH 20/21] backport #2292 --- DSharpPlus.targets | 2 +- .../Interaction/Application/DiscordApplicationCommand.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/DSharpPlus.targets b/DSharpPlus.targets index 35fc2b2d8a..89fef6cd1b 100644 --- a/DSharpPlus.targets +++ b/DSharpPlus.targets @@ -1,7 +1,7 @@ - 4.5.0 + 4.5.1 1591 9.0 True diff --git a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs index cc1110a6ff..f86592561b 100644 --- a/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs +++ b/DSharpPlus/Entities/Interaction/Application/DiscordApplicationCommand.cs @@ -128,11 +128,8 @@ public DiscordApplicationCommand(string name, string description, IEnumerable 100) throw new ArgumentException("Slash command description cannot exceed 100 characters.", nameof(description)); } - else + else if (type == ApplicationCommandType.UserContextMenu || type == ApplicationCommandType.MessageContextMenu) { - if (!string.IsNullOrWhiteSpace(description)) - throw new ArgumentException("Context menus do not support descriptions."); - if (options?.Any() ?? false) throw new ArgumentException("Context menus do not support options."); } From 7ac46eb91ca6abe068144a4881c591fc28da4b0d Mon Sep 17 00:00:00 2001 From: Livia-Valeriya Silvercrown Date: Wed, 16 Apr 2025 13:55:09 +0200 Subject: [PATCH 21/21] apparently the action is too old --- .github/workflows/publish_release_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml index d734e4593e..880d9ee2de 100644 --- a/.github/workflows/publish_release_master.yml +++ b/.github/workflows/publish_release_master.yml @@ -28,7 +28,7 @@ jobs: - name: Publish NuGet packages run: "dotnet nuget push \"build/*\" -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json" - name: Upload NuGet packages to GitHub Actions - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PR NuGet Packages path: build/*