diff --git a/.github/workflows/publish_release_master.yml b/.github/workflows/publish_release_master.yml
index 5da43bb075..880d9ee2de 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)
- 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')"
+ name: Publish Release
+ runs-on: "ubuntu-latest"
+ if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
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
- uses: actions/upload-artifact@v3
+ - name: Upload NuGet packages to GitHub Actions
+ uses: actions/upload-artifact@v4
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:
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..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
@@ -1060,21 +1062,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..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();
}
///
@@ -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.targets b/DSharpPlus.targets
index 168293733f..89fef6cd1b 100644
--- a/DSharpPlus.targets
+++ b/DSharpPlus.targets
@@ -1,7 +1,7 @@
- 4.4.0
+ 4.5.1
1591
9.0
True
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;
}
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 180bd93b03..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);
@@ -1030,13 +1052,8 @@ private void PopulateMessageReactionsAndCache(DiscordMessage message, TransportU
#region Disposal
- ~DiscordClient()
- {
- this.Dispose();
- }
-
-
private bool _disposed;
+
///
/// Disposes your DiscordClient.
///
@@ -1046,17 +1063,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 +1084,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..3091ee5734 100644
--- a/DSharpPlus/Clients/DiscordShardedClient.cs
+++ b/DSharpPlus/Clients/DiscordShardedClient.cs
@@ -241,24 +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);
- 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);
+ var client = new DiscordClient(cfg, restClient);
if (!this._shards.TryAdd(i, client))
throw new InvalidOperationException("Could not initialize shards.");
}
- return shardc;
+ return shardCount;
}
#endregion
@@ -402,12 +403,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 +672,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/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/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/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..f86592561b 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; }
@@ -126,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.");
}
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;
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()
{ }
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/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 abed35bb40..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,
@@ -677,9 +676,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 +683,7 @@ public void Dispose()
this._disposed = true;
- this.GlobalRateLimitEvent.Reset();
+ this.GlobalRateLimitEvent?.Reset();
if (this._bucketCleanerTokenSource?.IsCancellationRequested == false)
{
@@ -703,9 +699,9 @@ public void Dispose()
}
catch { }
- this.RoutesToHashes.Clear();
- this.HashesToBuckets.Clear();
- this.RequestQueue.Clear();
+ this.RoutesToHashes?.Clear();
+ this.HashesToBuckets?.Clear();
+ this.RequestQueue?.Clear();
}
}
}
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..963f1f174c
--- /dev/null
+++ b/DSharpPlus/Net/Serialization/DiscordPermissionsJsonConverter.cs
@@ -0,0 +1,51 @@
+// 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);
+
+ if (token == null || token.Type == JTokenType.Null)
+ {
+ return Permissions.None;
+ }
+
+ 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));
+ }
+}