diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 87a6e4b..f661292 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -32,6 +32,8 @@ jobs:
run: |
mkdir build
dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
+ dotnet pack X10D.DSharpPlus -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
+ dotnet pack X10D.Hosting -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
dotnet pack X10D.Unity -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }}
- name: Push NuGet Package to GitHub
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index c17716d..b257fbf 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -32,6 +32,8 @@ jobs:
run: |
mkdir build
dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
+ dotnet pack X10D.DSharpPlus -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
+ dotnet pack X10D.Hosting -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
dotnet pack X10D.Unity -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }}
- name: Push NuGet Package to GitHub
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1de44e8..4de3372 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,6 +32,8 @@ jobs:
run: |
mkdir build
dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
+ dotnet pack X10D.DSharpPlus -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
+ dotnet pack X10D.Hosting -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
dotnet pack X10D.Unity -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build
- name: Push NuGet Package to GitHub
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e17676..cd5246e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## 3.2.0
### Added
+- Added new library X10D.DSharpPlus
+- Added new library X10D.Hosting
+
- X10D: Added `MathUtility.InverseLerp(float, float, float)` and `MathUtility.InverseLerp(double, double, double)`
- X10D: Added `Circle`, `CircleF`, `Cuboid`, `Ellipse`, `EllipseF`, `Line3D`, `Line`, `LineF`, `Polygon`, `PolygonF`, `Polyhedron`, and `Sphere`, to complement System.Drawing structs such as `Point` and `Rectangle`
- X10D: Added `Color.Deconstruct()` - with optional alpha parameter
diff --git a/X10D.DSharpPlus/X10D.DSharpPlus.csproj b/X10D.DSharpPlus/X10D.DSharpPlus.csproj
new file mode 100644
index 0000000..27715d7
--- /dev/null
+++ b/X10D.DSharpPlus/X10D.DSharpPlus.csproj
@@ -0,0 +1,61 @@
+
+
+
+ net6.0;netstandard2.1
+ 10.0
+ true
+ true
+ Oliver Booth
+ en
+ https://github.com/oliverbooth/X10D
+ git
+ Extension methods on crack.
+ LICENSE.md
+ icon.png
+
+ dotnet extension-methods
+ true
+ 3.2.0
+ enable
+ true
+ true
+
+
+
+ $(VersionPrefix)-$(VersionSuffix)
+ $(VersionPrefix).0
+ $(VersionPrefix).0
+
+
+
+ $(VersionPrefix)-$(VersionSuffix).$(BuildNumber)
+ $(VersionPrefix).$(BuildNumber)
+ $(VersionPrefix).$(BuildNumber)
+
+
+
+ $(VersionPrefix)
+ $(VersionPrefix).0
+ $(VersionPrefix).0
+
+
+
+
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+
diff --git a/X10D.DSharpPlus/src/DiscordChannelExtensions.cs b/X10D.DSharpPlus/src/DiscordChannelExtensions.cs
new file mode 100644
index 0000000..48957c3
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordChannelExtensions.cs
@@ -0,0 +1,80 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordChannelExtensions
+{
+ ///
+ /// Gets the category of this channel.
+ ///
+ /// The channel whose category to retrieve.
+ ///
+ /// The category of , or itself if it is already a category;
+ /// if this channel is not defined in a category.
+ ///
+ /// is .
+ public static DiscordChannel? GetCategory(this DiscordChannel channel)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(channel);
+#else
+ if (channel is null)
+ {
+ throw new ArgumentNullException(nameof(channel));
+ }
+#endif
+
+ while (true)
+ {
+ if (channel.IsCategory)
+ {
+ return channel;
+ }
+
+ if (channel.Parent is not { } parent)
+ {
+ return null;
+ }
+
+ channel = parent;
+ }
+ }
+
+ ///
+ /// Normalizes a so that the internal client is assured to be a specified value.
+ ///
+ /// The to normalize.
+ /// The target client.
+ ///
+ /// A whose public values will match , but whose internal client
+ /// is .
+ ///
+ ///
+ /// is
+ /// -or-
+ /// is
+ ///
+ public static async Task NormalizeClientAsync(this DiscordChannel channel, DiscordClient client)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(channel);
+ ArgumentNullException.ThrowIfNull(client);
+#else
+ if (channel is null)
+ {
+ throw new ArgumentNullException(nameof(channel));
+ }
+
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+#endif
+
+ return await client.GetChannelAsync(channel.Id);
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordClientExtensions.cs b/X10D.DSharpPlus/src/DiscordClientExtensions.cs
new file mode 100644
index 0000000..5835d93
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordClientExtensions.cs
@@ -0,0 +1,34 @@
+using DSharpPlus;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordClientExtensions
+{
+ ///
+ /// Instructs the client to automatically join all existing threads, and any newly-created threads.
+ ///
+ /// The whose events should be subscribed.
+ ///
+ /// to automatically rejoin a thread if this client was removed; otherwise,
+ /// .
+ ///
+ public static void AutoJoinThreads(this DiscordClient client, bool rejoinIfRemoved = true)
+ {
+ client.GuildAvailable += (_, args) => args.Guild.JoinAllThreadsAsync();
+ client.ThreadCreated += (_, args) => args.Thread.JoinThreadAsync();
+
+ if (rejoinIfRemoved)
+ {
+ client.ThreadMembersUpdated += (_, args) =>
+ {
+ if (args.RemovedMembers.Any(m => m.Id == client.CurrentUser.Id))
+ return args.Thread.JoinThreadAsync();
+
+ return Task.CompletedTask;
+ };
+ }
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordEmbedBuilderExtensions.cs b/X10D.DSharpPlus/src/DiscordEmbedBuilderExtensions.cs
new file mode 100644
index 0000000..fb21b85
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordEmbedBuilderExtensions.cs
@@ -0,0 +1,211 @@
+using DSharpPlus.Entities;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordEmbedBuilderExtensions
+{
+ ///
+ /// Conditionally adds a field to the embed.
+ ///
+ /// The to modify.
+ /// The condition whose value is used to determine whether the field will be added.
+ /// The name of the embed field.
+ /// The value of the embed field.
+ /// to display this field inline; otherwise, .
+ /// The type of .
+ /// The current instance of ; that is, .
+ /// is .
+ public static DiscordEmbedBuilder AddFieldIf(
+ this DiscordEmbedBuilder builder,
+ bool condition,
+ string name,
+ T? value,
+ bool inline = false)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(builder);
+#else
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+#endif
+
+ if (condition)
+ {
+ builder.AddField(name, value?.ToString(), inline);
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Conditionally adds a field to the embed.
+ ///
+ /// The to modify.
+ /// The predicate whose return value is used to determine whether the field will be added.
+ /// The name of the embed field.
+ /// The value of the embed field.
+ /// to display this field inline; otherwise, .
+ /// The type of .
+ /// The current instance of ; that is, .
+ ///
+ /// is .
+ /// -or-
+ /// is .
+ ///
+ public static DiscordEmbedBuilder AddFieldIf(
+ this DiscordEmbedBuilder builder,
+ Func predicate,
+ string name,
+ T? value,
+ bool inline = false)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(predicate);
+#else
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (predicate is null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
+#endif
+
+ if (predicate.Invoke())
+ {
+ builder.AddField(name, value?.ToString(), inline);
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Conditionally adds a field to the embed.
+ ///
+ /// The to modify.
+ /// The predicate whose return value is used to determine whether the field will be added.
+ /// The name of the embed field.
+ /// The delegate whose return value will be used as the value of the embed field.
+ /// to display this field inline; otherwise, .
+ /// The return type of .
+ /// The current instance of ; that is, .
+ ///
+ /// is .
+ /// -or-
+ /// is .
+ /// -or-
+ /// is .
+ ///
+ public static DiscordEmbedBuilder AddFieldIf(
+ this DiscordEmbedBuilder builder,
+ Func predicate,
+ string name,
+ Func valueFactory,
+ bool inline = false)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(predicate);
+ ArgumentNullException.ThrowIfNull(valueFactory);
+#else
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (predicate is null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
+
+ if (valueFactory is null)
+ {
+ throw new ArgumentNullException(nameof(valueFactory));
+ }
+#endif
+
+ if (predicate.Invoke())
+ {
+ builder.AddField(name, valueFactory.Invoke()?.ToString(), inline);
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Conditionally adds a field to the embed.
+ ///
+ /// The to modify.
+ /// The condition whose value is used to determine whether the field will be added.
+ /// The name of the embed field.
+ /// The delegate whose return value will be used as the value of the embed field.
+ /// to display this field inline; otherwise, .
+ /// The return type of .
+ /// The current instance of ; that is, .
+ ///
+ /// is .
+ /// -or-
+ /// is .
+ ///
+ public static DiscordEmbedBuilder AddFieldIf(
+ this DiscordEmbedBuilder builder,
+ bool condition,
+ string name,
+ Func valueFactory,
+ bool inline = false)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(valueFactory);
+#else
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (valueFactory is null)
+ {
+ throw new ArgumentNullException(nameof(valueFactory));
+ }
+#endif
+
+ if (condition)
+ {
+ builder.AddField(name, valueFactory.Invoke()?.ToString(), inline);
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Sets the embed's author.
+ ///
+ /// The embed builder to modify.
+ /// The author.
+ /// The current instance of .
+ public static DiscordEmbedBuilder WithAuthor(this DiscordEmbedBuilder builder, DiscordUser user)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(user);
+#else
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (user is null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
+#endif
+
+ return builder.WithAuthor(user.Username, user.AvatarUrl);
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordGuildExtensions.cs b/X10D.DSharpPlus/src/DiscordGuildExtensions.cs
new file mode 100644
index 0000000..d5451f4
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordGuildExtensions.cs
@@ -0,0 +1,63 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordGuildExtensions
+{
+ ///
+ /// Joins all active threads in the guild that this client has permission to view.
+ ///
+ /// The guild whose active threads to join.
+ /// is .
+ public static async Task JoinAllThreadsAsync(this DiscordGuild guild)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(guild);
+#else
+ if (guild is null)
+ {
+ throw new ArgumentNullException(nameof(guild));
+ }
+#endif
+
+ await Task.WhenAll(guild.Threads.Values.Select(t => t.JoinThreadAsync()));
+ }
+
+ ///
+ /// Normalizes a so that the internal client is assured to be a specified value.
+ ///
+ /// The to normalize.
+ /// The target client.
+ ///
+ /// A whose public values will match , but whose internal client is
+ /// .
+ ///
+ ///
+ /// is
+ /// -or-
+ /// is
+ ///
+ public static async Task NormalizeClientAsync(this DiscordGuild guild, DiscordClient client)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(guild);
+ ArgumentNullException.ThrowIfNull(client);
+#else
+ if (guild is null)
+ {
+ throw new ArgumentNullException(nameof(guild));
+ }
+
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+#endif
+
+ return await client.GetGuildAsync(guild.Id);
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordMemberExtensions.cs b/X10D.DSharpPlus/src/DiscordMemberExtensions.cs
new file mode 100644
index 0000000..aede000
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordMemberExtensions.cs
@@ -0,0 +1,73 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordMemberExtensions
+{
+ ///
+ /// Returns a value indicating whether this member has the specified role.
+ ///
+ /// The member whose roles to search.
+ /// The role for which to check.
+ ///
+ /// if has the role; otherwise, .
+ ///
+ public static bool HasRole(this DiscordMember member, DiscordRole role)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(member);
+ ArgumentNullException.ThrowIfNull(role);
+#else
+ if (member is null)
+ {
+ throw new ArgumentNullException(nameof(member));
+ }
+
+ if (role is null)
+ {
+ throw new ArgumentNullException(nameof(role));
+ }
+#endif
+
+ return member.Roles.Contains(role);
+ }
+
+ ///
+ /// Normalizes a so that the internal client is assured to be a specified value.
+ ///
+ /// The to normalize.
+ /// The target client.
+ ///
+ /// A whose public values will match , but whose internal client
+ /// is .
+ ///
+ ///
+ /// is
+ /// -or-
+ /// is
+ ///
+ public static async Task NormalizeClientAsync(this DiscordMember member, DiscordClient client)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(member);
+ ArgumentNullException.ThrowIfNull(client);
+#else
+ if (member is null)
+ {
+ throw new ArgumentNullException(nameof(member));
+ }
+
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+#endif
+
+ DiscordGuild guild = await member.Guild.NormalizeClientAsync(client);
+ return await guild.GetMemberAsync(member.Id);
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordMessageExtensions.cs b/X10D.DSharpPlus/src/DiscordMessageExtensions.cs
new file mode 100644
index 0000000..e52b25f
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordMessageExtensions.cs
@@ -0,0 +1,89 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordMessageExtensions
+{
+ ///
+ /// Deletes this message after a specified delay.
+ ///
+ /// The message to delete.
+ /// The delay before deletion.
+ /// The reason for the deletion.
+ /// is .
+ public static async Task DeleteAfterAsync(this DiscordMessage message, TimeSpan delay, string? reason = null)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(message);
+#else
+ if (message is null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+#endif
+
+ await Task.Delay(delay);
+ await message.DeleteAsync(reason);
+ }
+
+ ///
+ /// Deletes the message as created by this task after a specified delay.
+ ///
+ /// The task whose result should be deleted.
+ /// The delay before deletion.
+ /// The reason for the deletion.
+ /// is .
+ public static async Task DeleteAfterAsync(this Task task, TimeSpan delay, string? reason = null)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(task);
+#else
+ if (task is null)
+ {
+ throw new ArgumentNullException(nameof(task));
+ }
+#endif
+
+ DiscordMessage message = await task;
+ await message.DeleteAfterAsync(delay, reason);
+ }
+
+ ///
+ /// Normalizes a so that the internal client is assured to be a specified value.
+ ///
+ /// The to normalize.
+ /// The target client.
+ ///
+ /// A whose public values will match , but whose internal client
+ /// is .
+ ///
+ ///
+ /// is
+ /// -or-
+ /// is
+ ///
+ public static async Task NormalizeClientAsync(this DiscordMessage message, DiscordClient client)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(message);
+ ArgumentNullException.ThrowIfNull(client);
+#else
+ if (message is null)
+ {
+ throw new ArgumentNullException(nameof(message));
+ }
+
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+#endif
+
+ DiscordChannel channel = await message.Channel.NormalizeClientAsync(client);
+ return await channel.GetMessageAsync(message.Id);
+ }
+}
diff --git a/X10D.DSharpPlus/src/DiscordUserExtensions.cs b/X10D.DSharpPlus/src/DiscordUserExtensions.cs
new file mode 100644
index 0000000..248a66c
--- /dev/null
+++ b/X10D.DSharpPlus/src/DiscordUserExtensions.cs
@@ -0,0 +1,158 @@
+using DSharpPlus;
+using DSharpPlus.Entities;
+using DSharpPlus.Exceptions;
+
+namespace X10D.DSharpPlus;
+
+///
+/// Extension methods for .
+///
+public static class DiscordUserExtensions
+{
+ ///
+ /// Returns the current as a member of the specified guild.
+ ///
+ /// The user to transform.
+ /// The guild whose member list to search.
+ ///
+ /// A whose is equal to , or
+ /// if this user is not in the specified .
+ ///
+ ///
+ /// is .
+ /// -or-
+ /// is .
+ ///
+ public static async Task GetAsMemberOfAsync(this DiscordUser user, DiscordGuild guild)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(user);
+ ArgumentNullException.ThrowIfNull(guild);
+#else
+ if (user is null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
+
+ if (guild is null)
+ {
+ throw new ArgumentNullException(nameof(guild));
+ }
+#endif
+
+ if (user is DiscordMember member && member.Guild == guild)
+ {
+ return member;
+ }
+
+ if (guild.Members.TryGetValue(user.Id, out member!))
+ {
+ return member;
+ }
+
+ try
+ {
+ return await guild.GetMemberAsync(user.Id);
+ }
+ catch (NotFoundException)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Returns the user's username with the discriminator, in the format username#discriminator.
+ ///
+ /// The user whose username and discriminator to retrieve.
+ /// A string in the format username#discriminator
+ /// is .
+ public static string GetUsernameWithDiscriminator(this DiscordUser user)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(user);
+#else
+ if (user is null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
+#endif
+
+ return $"{user.Username}#{user.Discriminator}";
+ }
+
+ ///
+ /// Returns a value indicating whether the current user is in the specified guild.
+ ///
+ /// The user to check.
+ /// The guild whose member list to search.
+ ///
+ /// if is a member of ; otherwise,
+ /// .
+ ///
+ public static async Task IsInGuildAsync(this DiscordUser user, DiscordGuild guild)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(user);
+ ArgumentNullException.ThrowIfNull(guild);
+#else
+ if (user is null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
+
+ if (guild is null)
+ {
+ throw new ArgumentNullException(nameof(guild));
+ }
+#endif
+
+ if (guild.Members.TryGetValue(user.Id, out _))
+ {
+ return true;
+ }
+
+ try
+ {
+ DiscordMember? member = await guild.GetMemberAsync(user.Id);
+ return member is not null;
+ }
+ catch (NotFoundException)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Normalizes a so that the internal client is assured to be a specified value.
+ ///
+ /// The to normalize.
+ /// The target client.
+ ///
+ /// A whose public values will match , but whose internal client is
+ /// .
+ ///
+ ///
+ /// is
+ /// -or-
+ /// is
+ ///
+ public static async Task NormalizeClientAsync(this DiscordUser user, DiscordClient client)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(user);
+ ArgumentNullException.ThrowIfNull(client);
+#else
+ if (user is null)
+ {
+ throw new ArgumentNullException(nameof(user));
+ }
+
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+#endif
+
+ return await client.GetGuildAsync(user.Id);
+ }
+}
diff --git a/X10D.Hosting/X10D.Hosting.csproj b/X10D.Hosting/X10D.Hosting.csproj
new file mode 100644
index 0000000..1c487ee
--- /dev/null
+++ b/X10D.Hosting/X10D.Hosting.csproj
@@ -0,0 +1,61 @@
+
+
+
+ net6.0;netstandard2.1
+ 10.0
+ true
+ true
+ Oliver Booth
+ en
+ https://github.com/oliverbooth/X10D
+ git
+ Extension methods on crack.
+ LICENSE.md
+ icon.png
+
+ dotnet extension-methods
+ true
+ 3.2.0
+ enable
+ true
+ true
+
+
+
+ $(VersionPrefix)-$(VersionSuffix)
+ $(VersionPrefix).0
+ $(VersionPrefix).0
+
+
+
+ $(VersionPrefix)-$(VersionSuffix).$(BuildNumber)
+ $(VersionPrefix).$(BuildNumber)
+ $(VersionPrefix).$(BuildNumber)
+
+
+
+ $(VersionPrefix)
+ $(VersionPrefix).0
+ $(VersionPrefix).0
+
+
+
+
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+
diff --git a/X10D.Hosting/src/DependencyInjection/ServiceCollectionExtensions.cs b/X10D.Hosting/src/DependencyInjection/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..9a0bafe
--- /dev/null
+++ b/X10D.Hosting/src/DependencyInjection/ServiceCollectionExtensions.cs
@@ -0,0 +1,35 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace X10D.Hosting.DependencyInjection;
+
+///
+/// Dependency injection extensions for .
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds an registration for the given type, while simultaneously adding it as a singleton.
+ ///
+ /// The to add the service to.
+ /// The type of the service to add.
+ /// A reference to this instance after the operation has completed.
+ public static IServiceCollection AddHostedSingleton(this IServiceCollection services)
+ where TService : class, IHostedService
+ {
+ services.AddSingleton();
+ return services.AddSingleton(provider => provider.GetRequiredService());
+ }
+
+ ///
+ /// Adds an registration for the given type, while simultaneously adding it as a singleton.
+ ///
+ /// The to add the service to.
+ /// The type of the service to register and the implementation to use.
+ /// A reference to this instance after the operation has completed.
+ public static IServiceCollection AddHostedSingleton(this IServiceCollection services, Type type)
+ {
+ services.AddSingleton(type);
+ return services.AddSingleton(provider => (IHostedService)provider.GetRequiredService(type));
+ }
+}
diff --git a/X10D.sln b/X10D.sln
index e6f0bcc..2c404b7 100644
--- a/X10D.sln
+++ b/X10D.sln
@@ -24,6 +24,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.Unity", "X10D.Unity\X1
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.SourceGenerator", "X10D.SourceGenerator\X10D.SourceGenerator.csproj", "{077A5D33-AD55-4C55-8A67-972CEBC32C7A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.DSharpPlus", "X10D.DSharpPlus\X10D.DSharpPlus.csproj", "{675D3B25-7EA0-4FC3-B513-8DF27874F2CF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.Hosting", "X10D.Hosting\X10D.Hosting.csproj", "{B04AF429-30CF-4B69-81BA-38F560CA9126}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -50,6 +54,14 @@ Global
{077A5D33-AD55-4C55-8A67-972CEBC32C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{077A5D33-AD55-4C55-8A67-972CEBC32C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{077A5D33-AD55-4C55-8A67-972CEBC32C7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {675D3B25-7EA0-4FC3-B513-8DF27874F2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {675D3B25-7EA0-4FC3-B513-8DF27874F2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {675D3B25-7EA0-4FC3-B513-8DF27874F2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {675D3B25-7EA0-4FC3-B513-8DF27874F2CF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B04AF429-30CF-4B69-81BA-38F560CA9126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B04AF429-30CF-4B69-81BA-38F560CA9126}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B04AF429-30CF-4B69-81BA-38F560CA9126}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B04AF429-30CF-4B69-81BA-38F560CA9126}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE