diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 840650cf8..1455267f9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,8 +7,7 @@ following guidelines when possible:
## Development Cycle
We prefer all changes to the library to be discussed beforehand,
-either in a GitHub issue, or in a discussion in our Discord channel
-with library regulars or other contributors.
+either in a GitHub issue, or in a discussion in our [Discord server](https://discord.gg/dnet)
Issues that are tagged as "up for grabs" are free to be picked up by
any member of the community.
diff --git a/README.md b/README.md
index e85216dbf..7b98a5f41 100644
--- a/README.md
+++ b/README.md
@@ -19,11 +19,11 @@
Discord.Net is an unofficial .NET API Wrapper for the Discord client (https://discord.com).
-## Documentation
+## 📄 Documentation
-- [Nightly](https://discordnet.dev)
+- https://discordnet.dev
-## Installation
+## 📥 Installation
### Stable (NuGet)
@@ -33,55 +33,78 @@ Our stable builds available from NuGet through the Discord.Net metapackage:
The individual components may also be installed from NuGet:
-- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/)
-- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/)
-- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/)
-- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/)
+- _Webhooks_
+ - [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/)
-### Unstable (MyGet)
+- _Text-Command & Interaction services._
+ - [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/)
+ - [Discord.Net.Interactions](https://www.nuget.org/packages/Discord.Net.Interactions/)
-Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`).
+- _Complete API coverage._
+ - [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/)
+ - [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/)
-### Unstable (Labs)
+- _The API core. Implements only entities and barebones functionality._
+ - [Discord.Net.Core](https://www.nuget.org/packages/Discord.Net.Core/)
-Labs builds are available on nuget (`https://www.nuget.org/packages/Discord.Net.Labs/`) and myget (`https://www.myget.org/F/discord-net-labs/api/v3/index.json`).
+### Unstable
-## Compiling
+Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`).
+These builds target the dev branch.
-In order to compile Discord.Net, you require the following:
+## 🛑 Known Issues
-### Using Visual Studio
+### WebSockets (Win7 and earlier)
-- [Visual Studio 2017](https://www.microsoft.com/net/core#windowsvs2017)
-- [.NET Core SDK](https://www.microsoft.com/net/download/core)
+.NET Core 1.1 does not support WebSockets on Win7 and earlier.
+This issue has been fixed since the release of .NET Core 2.1.
+It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms;
+alternatively, you may choose to install the
+[Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package.
-The .NET Core workload must be selected during Visual Studio installation.
+### TLS on .NET Framework.
-### Using Command Line
+Discord supports only TLS1.2+ on all their websites including the API since 07/19/2022.
+.NET Framework does not support this protocol by default.
+If you depend on .NET Framework, it is suggested to upgrade your project to `net6-windows`.
+This framework supports most of the windows-only features introduced by fx, and resolves startup errors from the TLS protocol mismatch.
-- [.NET Core SDK](https://www.microsoft.com/net/download/core)
+## 🗃️ Versioning Guarantees
-## Known Issues
+This library generally abides by [Semantic Versioning](https://semver.org). Packages are published in `MAJOR.MINOR.PATCH` version format.
-### WebSockets (Win7 and earlier)
+### Patch component
-.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package.
+An increment of the **PATCH** component always indicates that an internal-only change was made, generally a bugfix. These changes will not affect the public-facing API in any way, and are always guaranteed to be forward- and backwards-compatible with your codebase, any pre-compiled dependencies of your codebase.
-## Versioning Guarantees
+### Minor component
-This library generally abides by [Semantic Versioning](https://semver.org). Packages are published in MAJOR.MINOR.PATCH version format.
+An increment of the **MINOR** component indicates that some addition was made to the library,
+and this addition is not backwards-compatible with prior versions.
+However, Discord.Net **does not guarantee forward-compatibility** on minor additions.
+In other words, we permit a limited set of breaking changes on a minor version bump.
-An increment of the PATCH component always indicates that an internal-only change was made, generally a bugfix. These changes will not affect the public-facing API in any way, and are always guaranteed to be forward- and backwards-compatible with your codebase, any pre-compiled dependencies of your codebase.
+Due to the nature of the Discord API, we will oftentimes need to add a property to an entity to support the latest API changes.
+Discord.Net provides interfaces as a method of consuming entities; and as such, introducing a new field to an entity is technically a breaking change.
+Major version bumps generally indicate some major change to the library,
+and as such we are hesitant to bump the major version for every minor addition to the library.
+To compromise, we have decided that interfaces should be treated as **consumable only**,
+and your applications should typically not be implementing interfaces.
-An increment of the MINOR component indicates that some addition was made to the library, and this addition is not backwards-compatible with prior versions. However, Discord.Net **does not guarantee forward-compatibility** on minor additions. In other words, we permit a limited set of breaking changes on a minor version bump.
+> For applications where interfaces are implemented, such as in test mocks, we apologize for this inconsistency with SemVer.
-Due to the nature of the Discord API, we will oftentimes need to add a property to an entity to support the latest API changes. Discord.Net provides interfaces as a method of consuming entities; and as such, introducing a new field to an entity is technically a breaking change. Major version bumps generally indicate some major change to the library, and as such we are hesitant to bump the major version for every minor addition to the library. To compromise, we have decided that interfaces should be treated as **consumable only**, and your applications should typically not be implementing interfaces. (For applications where interfaces are implemented, such as in test mocks, we apologize for this inconsistency with SemVer).
+While we will never break the API (outside of interface changes) on minor builds,
+we will occasionally need to break the ABI, by introducing parameters to a method to match changes upstream with Discord.
+As such, a minor version increment may require you to recompile your code, and dependencies,
+such as addons, may also need to be recompiled and republished on the newer version.
+When a binary breaking change is made, the change will be noted in the release notes.
-Furthermore, while we will never break the API (outside of interface changes) on minor builds, we will occasionally need to break the ABI, by introducing parameters to a method to match changes upstream with Discord. As such, a minor version increment may require you to recompile your code, and dependencies, such as addons, may also need to be recompiled and republished on the newer version. When a binary breaking change is made, the change will be noted in the release notes.
+### Major component
-An increment of the MAJOR component indicates that breaking changes have been made to the library; consumers should check the release notes to determine what changes need to be made.
+An increment of the **MAJOR** component indicates that breaking changes have been made to the library;
+consumers should check the release notes to determine what changes need to be made.
-## Branches
+## 📚 Branches
### Release/X.X
diff --git a/docs/faq/basics/client-basics.md b/docs/faq/basics/client-basics.md
index ade33c35d..3f21dd165 100644
--- a/docs/faq/basics/client-basics.md
+++ b/docs/faq/basics/client-basics.md
@@ -36,7 +36,7 @@ _client = new DiscordSocketClient(config);
This includes intents that receive messages such as: `GatewayIntents.GuildMessages, GatewayIntents.DirectMessages`
- GuildMembers: An intent disabled by default, as you need to enable it in the [developer portal].
- GuildPresences: Also disabled by default, this intent together with `GuildMembers` are the only intents not included in `AllUnprivileged`.
-- All: All intents, it is ill adviced to use this without care, as it *can* cause a memory leak from presence.
+- All: All intents, it is ill advised to use this without care, as it _can_ cause a memory leak from presence.
The library will give responsive warnings if you specify unnecessary intents.
diff --git a/docs/guides/dependency_injection/basics.md b/docs/guides/dependency_injection/basics.md
new file mode 100644
index 000000000..c553ee68c
--- /dev/null
+++ b/docs/guides/dependency_injection/basics.md
@@ -0,0 +1,69 @@
+---
+uid: Guides.DI.Intro
+title: Introduction
+---
+
+# Dependency Injection
+
+Dependency injection is a feature not required in Discord.Net, but makes it a lot easier to use.
+It can be combined with a large number of other libraries, and gives you better control over your application.
+
+> Further into the documentation, Dependency Injection will be referred to as 'DI'.
+
+## Installation
+
+DI is not native to .NET. You need to install the extension packages to your project in order to use it:
+
+- [Meta](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/).
+- [Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/).
+
+> [!WARNING]
+> Downloading the abstractions package alone will not give you access to required classes to use DI properly.
+> Please install both packages, or choose to only install the meta package to implicitly install both.
+
+### Visual Package Manager:
+
+[Installing](images/manager.png)
+
+### Command Line:
+
+`PM> Install-Package Microsoft.Extensions.DependencyInjection`.
+
+> [!TIP]
+> ASP.NET already comes packed with all the necessary assemblies in its framework.
+> You do not require to install any additional NuGet packages to make full use of all features of DI in ASP.NET projects.
+
+## Getting started
+
+First of all, you will need to create an application based around dependency injection,
+which in order will be able to access and inject them across the project.
+
+[!code-csharp[Building the Program](samples/program.cs)]
+
+In order to freely pass around your dependencies in different classes,
+you will need to register them to a new `ServiceCollection` and build them into an `IServiceProvider` as seen above.
+The IServiceProvider then needs to be accessible by the startup file, so you can access your provider and manage them.
+
+[!code-csharp[Building the Collection](samples/collection.cs)]
+
+As shown above, an instance of `DiscordSocketConfig` is created, and added **before** the client itself is.
+Because the collection will prefer to create the highest populated constructor available with the services already present,
+it will prefer the constructor with the configuration, because you already added it.
+
+## Using your dependencies
+
+After building your provider in the Program class constructor, the provider is now available inside the instance you're actively using.
+Through the provider, we can ask for the DiscordSocketClient we registered earlier.
+
+[!code-csharp[Applying DI in RunAsync](samples/runasync.cs)]
+
+> [!WARNING]
+> Service constructors are not activated until the service is **first requested**.
+> An 'endpoint' service will have to be requested from the provider before it is activated.
+> If a service is requested with dependencies, its dependencies (if not already active) will be activated before the service itself is.
+
+## Injecting dependencies
+
+You can not only directly access the provider from a field or property, but you can also pass around instances to classes registered in the provider.
+There are multiple ways to do this. Please refer to the
+[Injection Documentation](Guides.DI.Injection) for further information.
diff --git a/docs/guides/dependency_injection/images/manager.png b/docs/guides/dependency_injection/images/manager.png
new file mode 100644
index 000000000..91791f7a0
Binary files /dev/null and b/docs/guides/dependency_injection/images/manager.png differ
diff --git a/docs/guides/dependency_injection/injection.md b/docs/guides/dependency_injection/injection.md
new file mode 100644
index 000000000..c7d40c479
--- /dev/null
+++ b/docs/guides/dependency_injection/injection.md
@@ -0,0 +1,44 @@
+---
+uid: Guides.DI.Injection
+title: Injection
+---
+
+# Injecting instances within the provider
+
+You can inject registered services into any class that is registered to the `IServiceProvider`.
+This can be done through property or constructor.
+
+> [!NOTE]
+> As mentioned above, the dependency *and* the target class have to be registered in order for the serviceprovider to resolve it.
+
+## Injecting through a constructor
+
+Services can be injected from the constructor of the class.
+This is the preferred approach, because it automatically locks the readonly field in place with the provided service and isn't accessible outside of the class.
+
+[!code-csharp[Property Injection(samples/property-injecting.cs)]]
+
+## Injecting through properties
+
+Injecting through properties is also allowed as follows.
+
+[!code-csharp[Property Injection](samples/property-injecting.cs)]
+
+> [!WARNING]
+> Dependency Injection will not resolve missing services in property injection, and it will not pick a constructor instead.
+> If a publically accessible property is attempted to be injected and its service is missing, the application will throw an error.
+
+## Using the provider itself
+
+You can also access the provider reference itself from injecting it into a class. There are multiple use cases for this:
+
+- Allowing libraries (Like Discord.Net) to access your provider internally.
+- Injecting optional dependencies.
+- Calling methods on the provider itself if necessary, this is often done for creating scopes.
+
+[!code-csharp[Provider Injection](samples/provider.cs)]
+
+> [!NOTE]
+> It is important to keep in mind that the provider will pick the 'biggest' available constructor.
+> If you choose to introduce multiple constructors,
+> keep in mind that services missing from one constructor may have the provider pick another one that *is* available instead of throwing an exception.
diff --git a/docs/guides/dependency_injection/samples/access-activator.cs b/docs/guides/dependency_injection/samples/access-activator.cs
new file mode 100644
index 000000000..29e71e894
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/access-activator.cs
@@ -0,0 +1,9 @@
+async Task RunAsync()
+{
+ //...
+
+ await _serviceProvider.GetRequiredService()
+ .ActivateAsync();
+
+ //...
+}
diff --git a/docs/guides/dependency_injection/samples/collection.cs b/docs/guides/dependency_injection/samples/collection.cs
new file mode 100644
index 000000000..4d0457dc9
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/collection.cs
@@ -0,0 +1,13 @@
+static IServiceProvider CreateServices()
+{
+ var config = new DiscordSocketConfig()
+ {
+ //...
+ };
+
+ var collection = new ServiceCollection()
+ .AddSingleton(config)
+ .AddSingleton();
+
+ return collection.BuildServiceProvider();
+}
diff --git a/docs/guides/dependency_injection/samples/ctor-injecting.cs b/docs/guides/dependency_injection/samples/ctor-injecting.cs
new file mode 100644
index 000000000..c412bd29c
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/ctor-injecting.cs
@@ -0,0 +1,14 @@
+public class ClientHandler
+{
+ private readonly DiscordSocketClient _client;
+
+ public ClientHandler(DiscordSocketClient client)
+ {
+ _client = client;
+ }
+
+ public async Task ConfigureAsync()
+ {
+ //...
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/enumeration.cs b/docs/guides/dependency_injection/samples/enumeration.cs
new file mode 100644
index 000000000..cc8c617f3
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/enumeration.cs
@@ -0,0 +1,18 @@
+public class ServiceActivator
+{
+ // This contains *all* registered services of serviceType IService
+ private readonly IEnumerable _services;
+
+ public ServiceActivator(IEnumerable services)
+ {
+ _services = services;
+ }
+
+ public async Task ActivateAsync()
+ {
+ foreach(var service in _services)
+ {
+ await service.StartAsync();
+ }
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/implicit-registration.cs b/docs/guides/dependency_injection/samples/implicit-registration.cs
new file mode 100644
index 000000000..52f84228b
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/implicit-registration.cs
@@ -0,0 +1,12 @@
+public static ServiceCollection RegisterImplicitServices(this ServiceCollection collection, Type interfaceType, Type activatorType)
+{
+ // Get all types in the executing assembly. There are many ways to do this, but this is fastest.
+ foreach (var type in typeof(Program).Assembly.GetTypes())
+ {
+ if (interfaceType.IsAssignableFrom(type) && !type.IsAbstract)
+ collection.AddSingleton(interfaceType, type);
+ }
+
+ // Register the activator so you can activate the instances.
+ collection.AddSingleton(activatorType);
+}
diff --git a/docs/guides/dependency_injection/samples/modules.cs b/docs/guides/dependency_injection/samples/modules.cs
new file mode 100644
index 000000000..2fadc13d4
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/modules.cs
@@ -0,0 +1,16 @@
+public class MyModule : InteractionModuleBase
+{
+ private readonly MyService _service;
+
+ public MyModule(MyService service)
+ {
+ _service = service;
+ }
+
+ [SlashCommand("things", "Shows things")]
+ public async Task ThingsAsync()
+ {
+ var str = string.Join("\n", _service.Things)
+ await RespondAsync(str);
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/program.cs b/docs/guides/dependency_injection/samples/program.cs
new file mode 100644
index 000000000..6d985319a
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/program.cs
@@ -0,0 +1,24 @@
+public class Program
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ public Program()
+ {
+ _serviceProvider = CreateProvider();
+ }
+
+ static void Main(string[] args)
+ => new Program().RunAsync(args).GetAwaiter().GetResult();
+
+ static IServiceProvider CreateProvider()
+ {
+ var collection = new ServiceCollection();
+ //...
+ return collection.BuildServiceProvider();
+ }
+
+ async Task RunAsync(string[] args)
+ {
+ //...
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/property-injecting.cs b/docs/guides/dependency_injection/samples/property-injecting.cs
new file mode 100644
index 000000000..c0c50e150
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/property-injecting.cs
@@ -0,0 +1,9 @@
+public class ClientHandler
+{
+ public DiscordSocketClient Client { get; set; }
+
+ public async Task ConfigureAsync()
+ {
+ //...
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/provider.cs b/docs/guides/dependency_injection/samples/provider.cs
new file mode 100644
index 000000000..26b600b9d
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/provider.cs
@@ -0,0 +1,26 @@
+public class UtilizingProvider
+{
+ private readonly IServiceProvider _provider;
+ private readonly AnyService _service;
+
+ // This service is allowed to be null because it is only populated if the service is actually available in the provider.
+ private readonly AnyOtherService? _otherService;
+
+ // This constructor injects only the service provider,
+ // and uses it to populate the other dependencies.
+ public UtilizingProvider(IServiceProvider provider)
+ {
+ _provider = provider;
+ _service = provider.GetRequiredService();
+ _otherService = provider.GetService();
+ }
+
+ // This constructor injects the service provider, and AnyService,
+ // making sure that AnyService is not null without having to call GetRequiredService
+ public UtilizingProvider(IServiceProvider provider, AnyService service)
+ {
+ _provider = provider;
+ _service = service;
+ _otherService = provider.GetService();
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/runasync.cs b/docs/guides/dependency_injection/samples/runasync.cs
new file mode 100644
index 000000000..d24efc83e
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/runasync.cs
@@ -0,0 +1,17 @@
+async Task RunAsync(string[] args)
+{
+ // Request the instance from the client.
+ // Because we're requesting it here first, its targetted constructor will be called and we will receive an active instance.
+ var client = _services.GetRequiredService();
+
+ client.Log += async (msg) =>
+ {
+ await Task.CompletedTask;
+ Console.WriteLine(msg);
+ }
+
+ await client.LoginAsync(TokenType.Bot, "");
+ await client.StartAsync();
+
+ await Task.Delay(Timeout.Infinite);
+}
diff --git a/docs/guides/dependency_injection/samples/scoped.cs b/docs/guides/dependency_injection/samples/scoped.cs
new file mode 100644
index 000000000..9942f8d8e
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/scoped.cs
@@ -0,0 +1,6 @@
+
+// With serviceType:
+collection.AddScoped();
+
+// Without serviceType:
+collection.AddScoped();
diff --git a/docs/guides/dependency_injection/samples/service-registration.cs b/docs/guides/dependency_injection/samples/service-registration.cs
new file mode 100644
index 000000000..f6e4d22dd
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/service-registration.cs
@@ -0,0 +1,21 @@
+static IServiceProvider CreateServices()
+{
+ var config = new DiscordSocketConfig()
+ {
+ //...
+ };
+
+ // X represents either Interaction or Command, as it functions the exact same for both types.
+ var servConfig = new XServiceConfig()
+ {
+ //...
+ }
+
+ var collection = new ServiceCollection()
+ .AddSingleton(config)
+ .AddSingleton()
+ .AddSingleton(servConfig)
+ .AddSingleton();
+
+ return collection.BuildServiceProvider();
+}
diff --git a/docs/guides/dependency_injection/samples/services.cs b/docs/guides/dependency_injection/samples/services.cs
new file mode 100644
index 000000000..2e5235b69
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/services.cs
@@ -0,0 +1,9 @@
+public class MyService
+{
+ public List Things { get; }
+
+ public MyService()
+ {
+ Things = new();
+ }
+}
diff --git a/docs/guides/dependency_injection/samples/singleton.cs b/docs/guides/dependency_injection/samples/singleton.cs
new file mode 100644
index 000000000..f395d743e
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/singleton.cs
@@ -0,0 +1,6 @@
+
+// With serviceType:
+collection.AddSingleton();
+
+// Without serviceType:
+collection.AddSingleton();
diff --git a/docs/guides/dependency_injection/samples/transient.cs b/docs/guides/dependency_injection/samples/transient.cs
new file mode 100644
index 000000000..ae1e1a5d8
--- /dev/null
+++ b/docs/guides/dependency_injection/samples/transient.cs
@@ -0,0 +1,6 @@
+
+// With serviceType:
+collection.AddTransient();
+
+// Without serviceType:
+collection.AddTransient();
diff --git a/docs/guides/dependency_injection/scaling.md b/docs/guides/dependency_injection/scaling.md
new file mode 100644
index 000000000..356fb7c72
--- /dev/null
+++ b/docs/guides/dependency_injection/scaling.md
@@ -0,0 +1,39 @@
+---
+uid: Guides.DI.Scaling
+title: Scaling your DI
+---
+
+# Scaling your DI
+
+Dependency injection has a lot of use cases, and is very suitable for scaled applications.
+There are a few ways to make registering & using services easier in large amounts.
+
+## Using a range of services.
+
+If you have a lot of services that all have the same use such as handling an event or serving a module,
+you can register and inject them all at once by some requirements:
+
+- All classes need to inherit a single interface or abstract type.
+- While not required, it is preferred if the interface and types share a method to call on request.
+- You need to register a class that all the types can be injected into.
+
+### Registering implicitly
+
+Registering all the types is done through getting all types in the assembly and checking if they inherit the target interface.
+
+[!code-csharp[Registering](samples/implicit-registration.cs)]
+
+> [!NOTE]
+> As seen above, the interfaceType and activatorType are undefined. For our usecase below, these are `IService` and `ServiceActivator` in order.
+
+### Using implicit dependencies
+
+In order to use the implicit dependencies, you have to get access to the activator you registered earlier.
+
+[!code-csharp[Accessing the activator](samples/access-activator.cs)]
+
+When the activator is accessed and the `ActivateAsync()` method is called, the following code will be executed:
+
+[!code-csharp[Executing the activator](samples/enumeration.cs)]
+
+As a result of this, all the services that were registered with `IService` as its implementation type will execute their starting code, and start up.
diff --git a/docs/guides/dependency_injection/services.md b/docs/guides/dependency_injection/services.md
new file mode 100644
index 000000000..e021a88be
--- /dev/null
+++ b/docs/guides/dependency_injection/services.md
@@ -0,0 +1,48 @@
+---
+uid: Guides.DI.Services
+title: Using DI in Interaction & Command Frameworks
+---
+
+# DI in the Interaction- & Command Service
+
+For both the Interaction- and Command Service modules, DI is quite straight-forward to use.
+
+You can inject any service into modules without the modules having to be registered to the provider.
+Discord.Net resolves your dependencies internally.
+
+> [!WARNING]
+> The way DI is used in the Interaction- & Command Service are nearly identical, except for one detail:
+> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies)
+
+## Registering the Service
+
+Thanks to earlier described behavior of allowing already registered members as parameters of the available ctors,
+The socket client & configuration will automatically be acknowledged and the XService(client, config) overload will be used.
+
+[!code-csharp[Service Registration](samples/service-registration.cs)]
+
+## Usage in modules
+
+In the constructor of your module, any parameters will be filled in by
+the @System.IServiceProvider that you've passed.
+
+Any publicly settable properties will also be filled in the same
+manner.
+
+[!code-csharp[Module Injection](samples/modules.cs)]
+
+If you accept `Command/InteractionService` or `IServiceProvider` as a parameter in your constructor or as an injectable property,
+these entries will be filled by the `Command/InteractionService` that the module is loaded from and the `IServiceProvider` that is passed into it respectively.
+
+> [!NOTE]
+> Annotating a property with a [DontInjectAttribute] attribute will
+> prevent the property from being injected.
+
+## Services
+
+Because modules are transient of nature and will reinstantiate on every request,
+it is suggested to create a singleton service behind it to hold values across multiple command executions.
+
+[!code-csharp[Services](samples/services.cs)]
+
+
diff --git a/docs/guides/dependency_injection/types.md b/docs/guides/dependency_injection/types.md
new file mode 100644
index 000000000..e539d0695
--- /dev/null
+++ b/docs/guides/dependency_injection/types.md
@@ -0,0 +1,52 @@
+---
+uid: Guides.DI.Dependencies
+title: Types of Dependencies
+---
+
+# Dependency Types
+
+There are 3 types of dependencies to learn to use. Several different usecases apply for each.
+
+> [!WARNING]
+> When registering types with a serviceType & implementationType,
+> only the serviceType will be available for injection, and the implementationType will be used for the underlying instance.
+
+## Singleton
+
+A singleton service creates a single instance when first requested, and maintains that instance across the lifetime of the application.
+Any values that are changed within a singleton will be changed across all instances that depend on it, as they all have the same reference to it.
+
+### Registration:
+
+[!code-csharp[Singleton Example](samples/singleton.cs)]
+
+> [!NOTE]
+> Types like the Discord client and Interaction/Command services are intended to be singleton,
+> as they should last across the entire app and share their state with all references to the object.
+
+## Scoped
+
+A scoped service creates a new instance every time a new service is requested, but is kept across the 'scope'.
+As long as the service is in view for the created scope, the same instance is used for all references to the type.
+This means that you can reuse the same instance during execution, and keep the services' state for as long as the request is active.
+
+### Registration:
+
+[!code-csharp[Scoped Example](samples/scoped.cs)]
+
+> [!NOTE]
+> Without using HTTP or libraries like EFCORE, scopes are often unused in Discord bots.
+> They are most commonly used for handling HTTP and database requests.
+
+## Transient
+
+A transient service is created every time it is requested, and does not share its state between references within the target service.
+It is intended for lightweight types that require little state, to be disposed quickly after execution.
+
+### Registration:
+
+[!code-csharp[Transient Example](samples/transient.cs)]
+
+> [!NOTE]
+> Discord.Net modules behave exactly as transient types, and are intended to only last as long as the command execution takes.
+> This is why it is suggested for apps to use singleton services to keep track of cross-execution data.
diff --git a/docs/guides/deployment/deployment.md b/docs/guides/deployment/deployment.md
index 0491e841d..4313e85b4 100644
--- a/docs/guides/deployment/deployment.md
+++ b/docs/guides/deployment/deployment.md
@@ -47,6 +47,12 @@ enough. Here is a list of recommended VPS provider.
* Location(s):
* Europe: Lithuania
* Based in: Europe
+* [ServerStarter.Host](https://serverstarter.host/clients/store/discord-bots)
+ * Description: Bot hosting with a panel for quick deployment and
+ no Linux knowledge required.
+ * Location(s):
+ * America: United States
+ * Based in: United States
## .NET Core Deployment
@@ -100,4 +106,4 @@ Windows 10 x64 based machine:
* `dotnet publish -c Release -r win10-x64`
[.NET Core application deployment]: https://docs.microsoft.com/en-us/dotnet/core/deploying/
-[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
\ No newline at end of file
+[Runtime ID]: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md
index 0dde72380..d07787b67 100644
--- a/docs/guides/getting_started/installing.md
+++ b/docs/guides/getting_started/installing.md
@@ -30,17 +30,25 @@ other limitations, you may also consider targeting [.NET Framework]
[.net framework]: https://docs.microsoft.com/en-us/dotnet/framework/get-started/
[additional steps]: #installing-on-net-standard-11
-## Installing with NuGet
+## Installing
Release builds of Discord.Net will be published to the
[official NuGet feed].
-Development builds of Discord.Net, as well as add-ons, will be
-published to our [MyGet feed]. See
-@Guides.GettingStarted.Installation.Nightlies to learn more.
+### Experimental/Development
-[official nuget feed]: https://nuget.org
-[myget feed]: https://www.myget.org/feed/Packages/discord-net
+Development builds of Discord.Net will be
+published to our [MyGet feed]. The MyGet feed can be used to run the latest dev branch builds.
+It is not advised to use MyGet packages in a production environment, as changes may be made that negatively affect certain library functions.
+
+### Labs
+
+This exterior branch of Discord.Net has been deprecated and is no longer supported.
+If you have used Discord.Net-Labs in the past, you are advised to update to the latest version of Discord.Net.
+All features in Labs are implemented in the main repository.
+
+[official NuGet feed]: https://nuget.org
+[MyGet feed]: https://www.myget.org/feed/Packages/discord-net
### [Using Visual Studio](#tab/vs-install)
diff --git a/docs/guides/getting_started/labs.md b/docs/guides/getting_started/labs.md
deleted file mode 100644
index e52014312..000000000
--- a/docs/guides/getting_started/labs.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-uid: Guides.GettingStarted.Installation.Labs
-title: Installing Labs builds
----
-
-# Installing Discord.NET Labs
-
-Discord.NET Labs is the experimental repository that introduces new features & chips away at all bugs until ready for merging into Discord.NET.
-Are you looking to test or play with new features?
-
-> [!IMPORTANT]
-> It is very ill advised to use Discord.NET Labs in a production environment normally,
-> considering it can include bugs that have not been discovered yet, as features are freshly added.
-> However if approached correctly, will work as a pre-release to Discord.NET.
-> Make sure to report any bugs at the Labs [repository] or on [Discord]
-
-[Discord]: https://discord.gg/dnet
-[repository]: https://github.com/Discord-Net-Labs/Discord.Net-Labs
-
-## Installation:
-
-[NuGet] - This only includes releases, on which features are ready to test.
-
-> [!NOTE]
-> Installing NuGet packages is covered fully at [Installing Discord NET](xref:Guides.GettingStarted.Installation)
-
-[MyGet] - Available for current builds and unreleased features.
-
-[NuGet]: https://www.nuget.org/packages/Discord.Net.Labs/
-[MyGet]: https://www.myget.org/feed/Packages/discord-net-labs
diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md
index 0f24edd6f..facf1c790 100644
--- a/docs/guides/getting_started/terminology.md
+++ b/docs/guides/getting_started/terminology.md
@@ -8,18 +8,22 @@ title: Terminology
## Preface
Most terms for objects remain the same between 0.9 and 1.0 and above.
-The major difference is that the ``Server`` is now called ``Guild``
+The major difference is that the `Server` is now called `Guild`
to stay in line with Discord internally.
## Implementation Specific Entities
Discord.Net is split into a core library and two different
implementations - `Discord.Net.Core`, `Discord.Net.Rest`, and
-`Discord.Net.WebSockets`.
+`Discord.Net.WebSocket`.
-As a bot developer, you will only need to use `Discord.Net.WebSockets`,
+You will typically only need to use `Discord.Net.WebSocket`,
but you should be aware of the differences between them.
+> [!TIP]
+> If you are looking to implement Rest based interactions, or handle calls over REST in any other way,
+> `Discord.Net.Rest` is the resource most applicable to you.
+
`Discord.Net.Core` provides a set of interfaces that models Discord's
API. These interfaces are consistent throughout all implementations of
Discord.Net, and if you are writing an implementation-agnostic library
@@ -33,4 +37,4 @@ implementation are prefixed with `Rest` (e.g., `RestChannel`).
`Discord.Net.WebSocket` provides a set of concrete classes that are
used primarily with Discord's WebSocket API or entities that are kept
in cache. When developing bots, you will be using this implementation.
-All entities are prefixed with `Socket` (e.g., `SocketChannel`).
\ No newline at end of file
+All entities are prefixed with `Socket` (e.g., `SocketChannel`).
diff --git a/docs/guides/int_framework/dependency-injection.md b/docs/guides/int_framework/dependency-injection.md
deleted file mode 100644
index 31d001f4b..000000000
--- a/docs/guides/int_framework/dependency-injection.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-uid: Guides.IntFw.DI
-title: Dependency Injection
----
-
-# Dependency Injection
-
-Dependency injection in the Interaction Service is mostly based on that of the Text-based command service,
-for which further information is found [here](xref:Guides.TextCommands.DI).
-
-> [!NOTE]
-> The 2 are nearly identical, except for one detail:
-> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies)
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index 54e9086a1..5cf38bff1 100644
--- a/docs/guides/int_framework/intro.md
+++ b/docs/guides/int_framework/intro.md
@@ -279,8 +279,8 @@ Meaning, the constructor parameters and public settable properties of a module w
For more information on dependency injection, read the [DependencyInjection] guides.
> [!NOTE]
-> On every command execution, module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net.
-> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns.
+> On every command execution, if the 'AutoServiceScopes' option is enabled in the config , module dependencies are resolved using a new service scope which allows you to utilize scoped service instances, just like in Asp.Net.
+> Including the precondition checks, every module method is executed using the same service scope and service scopes are disposed right after the `AfterExecute` method returns. This doesn't apply to methods other than `ExecuteAsync()`.
## Module Groups
@@ -291,6 +291,11 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can
> Although creating nested module stuctures are allowed,
> you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy.
+> [!NOTE]
+> To not use the command group's name as a prefix for component or modal interaction's custom id set `ignoreGroupNames` parameter to `true` in classes with [GroupAttribute]
+>
+> However, you have to be careful to prevent overlapping ids of buttons and modals
+
[!code-csharp[Command Group Example](samples/intro/groupmodule.cs)]
## Executing Commands
@@ -303,8 +308,19 @@ Any of the following socket events can be used to execute commands:
- [AutocompleteExecuted]
- [UserCommandExecuted]
- [MessageCommandExecuted]
+- [ModalExecuted]
+
+These events will trigger for the specific type of interaction they inherit their name from. The [InteractionCreated] event will trigger for all.
+An example of executing a command from an event can be seen here:
-Commands can be either executed on the gateway thread or on a seperate thread from the thread pool. This behaviour can be configured by changing the *RunMode* property of `InteractionServiceConfig` or by setting the *runMode* parameter of a command attribute.
+[!code-csharp[Command Event Example](samples/intro/event.cs)]
+
+Commands can be either executed on the gateway thread or on a seperate thread from the thread pool.
+This behaviour can be configured by changing the `RunMode` property of `InteractionServiceConfig` or by setting the *runMode* parameter of a command attribute.
+
+> [!WARNING]
+> In the example above, no form of post-execution is presented.
+> Please carefully read the [Post Execution Documentation] for the best approach in resolving the result based on your `RunMode`.
You can also configure the way [InteractionService] executes the commands.
By default, commands are executed using `ConstructorInfo.Invoke()` to create module instances and
@@ -358,7 +374,7 @@ delegate can be used to create HTTP responses from a deserialized json object st
- Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...).
[AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion
-[DependencyInjection]: xref:Guides.TextCommands.DI
+[DependencyInjection]: xref:Guides.DI.Intro
[GroupAttribute]: xref:Discord.Interactions.GroupAttribute
[InteractionService]: xref:Discord.Interactions.InteractionService
@@ -371,6 +387,7 @@ delegate can be used to create HTTP responses from a deserialized json object st
[AutocompleteExecuted]: xref:Discord.WebSocket.BaseSocketClient
[UserCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient
[MessageCommandExecuted]: xref:Discord.WebSocket.BaseSocketClient
+[ModalExecuted]: xref:Discord.WebSocket.BaseSocketClient
[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient
[DiscordRestClient]: xref:Discord.Rest.DiscordRestClient
[SocketInteractionContext]: xref:Discord.Interactions.SocketInteractionContext
diff --git a/docs/guides/int_framework/samples/intro/event.cs b/docs/guides/int_framework/samples/intro/event.cs
new file mode 100644
index 000000000..0c9f032b9
--- /dev/null
+++ b/docs/guides/int_framework/samples/intro/event.cs
@@ -0,0 +1,14 @@
+// Theres multiple ways to subscribe to the event, depending on your application. Please use the approach fit to your type of client.
+// DiscordSocketClient:
+_socketClient.InteractionCreated += async (x) =>
+{
+ var ctx = new SocketInteractionContext(_socketClient, x);
+ await _interactionService.ExecuteCommandAsync(ctx, _serviceProvider);
+}
+
+// DiscordShardedClient:
+_shardedClient.InteractionCreated += async (x) =>
+{
+ var ctx = new ShardedInteractionContext(_shardedClient, x);
+ await _interactionService.ExecuteCommandAsync(ctx, _serviceProvider);
+}
diff --git a/docs/guides/int_framework/samples/intro/groupmodule.cs b/docs/guides/int_framework/samples/intro/groupmodule.cs
index f0d992aff..a07b2e4d8 100644
--- a/docs/guides/int_framework/samples/intro/groupmodule.cs
+++ b/docs/guides/int_framework/samples/intro/groupmodule.cs
@@ -16,6 +16,11 @@ public class CommandGroupModule : InteractionModuleBase await RespondAsync(input);
+ => await RespondAsync(input, components: new ComponentBuilder().WithButton("Echo", $"echoButton_{input}").Build());
+
+ // Component interaction with ignoreGroupNames set to true
+ [ComponentInteraction("echoButton_*", true)]
+ public async Task EchoButton(string input)
+ => await RespondAsync(input);
}
}
\ No newline at end of file
diff --git a/docs/guides/int_framework/samples/intro/modal.cs b/docs/guides/int_framework/samples/intro/modal.cs
index 65cc81abf..8a6ba9d8a 100644
--- a/docs/guides/int_framework/samples/intro/modal.cs
+++ b/docs/guides/int_framework/samples/intro/modal.cs
@@ -12,7 +12,9 @@ public class FoodModal : IModal
[ModalTextInput("food_name", placeholder: "Pizza", maxLength: 20)]
public string Food { get; set; }
- // Additional paremeters can be specified to further customize the input.
+ // Additional paremeters can be specified to further customize the input.
+ // Parameters can be optional
+ [RequiredInput(false)]
[InputLabel("Why??")]
[ModalTextInput("food_reason", TextInputStyle.Paragraph, "Kuz it's tasty", maxLength: 500)]
public string Reason { get; set; }
@@ -22,10 +24,15 @@ public class FoodModal : IModal
[ModalInteraction("food_menu")]
public async Task ModalResponse(FoodModal modal)
{
+ // Check if "Why??" field is populated
+ string reason = string.IsNullOrWhiteSpace(modal.Reason)
+ ? "."
+ : $" because {modal.Reason}";
+
// Build the message to send.
string message = "hey @everyone, I just learned " +
$"{Context.User.Mention}'s favorite food is " +
- $"{modal.Food} because {modal.Reason}.";
+ $"{modal.Food}{reason}";
// Specify the AllowedMentions so we don't actually ping everyone.
AllowedMentions mentions = new();
diff --git a/docs/guides/text_commands/dependency-injection.md b/docs/guides/text_commands/dependency-injection.md
deleted file mode 100644
index 3253643ef..000000000
--- a/docs/guides/text_commands/dependency-injection.md
+++ /dev/null
@@ -1,51 +0,0 @@
----
-uid: Guides.TextCommands.DI
-title: Dependency Injection
----
-
-# Dependency Injection
-
-The Text Command Service is bundled with a very barebone Dependency
-Injection service for your convenience. It is recommended that you use
-DI when writing your modules.
-
-> [!WARNING]
-> If you were brought here from the Interaction Service guides,
-> make sure to replace all namespaces that imply `Discord.Commands` with `Discord.Interactions`
-
-## Setup
-
-1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection.
-2. Add the dependencies to the service collection that you wish
- to use in the modules.
-3. Build the service collection into a service provider.
-4. Pass the service collection into @Discord.Commands.CommandService.AddModulesAsync* / @Discord.Commands.CommandService.AddModuleAsync* , @Discord.Commands.CommandService.ExecuteAsync* .
-
-### Example - Setting up Injection
-
-[!code-csharp[IServiceProvider Setup](samples/dependency-injection/dependency_map_setup.cs)]
-
-## Usage in Modules
-
-In the constructor of your module, any parameters will be filled in by
-the @System.IServiceProvider that you've passed.
-
-Any publicly settable properties will also be filled in the same
-manner.
-
-> [!NOTE]
-> Annotating a property with a [DontInjectAttribute] attribute will
-> prevent the property from being injected.
-
-> [!NOTE]
-> If you accept `CommandService` or `IServiceProvider` as a parameter
-> in your constructor or as an injectable property, these entries will
-> be filled by the `CommandService` that the module is loaded from and
-> the `IServiceProvider` that is passed into it respectively.
-
-### Example - Injection in Modules
-
-[!code-csharp[Injection Modules](samples/dependency-injection/dependency_module.cs)]
-[!code-csharp[Disallow Dependency Injection](samples/dependency-injection/dependency_module_noinject.cs)]
-
-[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute
diff --git a/docs/guides/text_commands/intro.md b/docs/guides/text_commands/intro.md
index 6632c127a..1113b0821 100644
--- a/docs/guides/text_commands/intro.md
+++ b/docs/guides/text_commands/intro.md
@@ -187,7 +187,7 @@ service provider.
### Module Constructors
-Modules are constructed using [Dependency Injection](xref:Guides.TextCommands.DI). Any parameters
+Modules are constructed using [Dependency Injection](xref:Guides.DI.Intro). Any parameters
that are placed in the Module's constructor must be injected into an
@System.IServiceProvider first.
diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs
deleted file mode 100644
index 16ca479db..000000000
--- a/docs/guides/text_commands/samples/dependency-injection/dependency_map_setup.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-public class Initialize
-{
- private readonly CommandService _commands;
- private readonly DiscordSocketClient _client;
-
- // Ask if there are existing CommandService and DiscordSocketClient
- // instance. If there are, we retrieve them and add them to the
- // DI container; if not, we create our own.
- public Initialize(CommandService commands = null, DiscordSocketClient client = null)
- {
- _commands = commands ?? new CommandService();
- _client = client ?? new DiscordSocketClient();
- }
-
- public IServiceProvider BuildServiceProvider() => new ServiceCollection()
- .AddSingleton(_client)
- .AddSingleton(_commands)
- // You can pass in an instance of the desired type
- .AddSingleton(new NotificationService())
- // ...or by using the generic method.
- //
- // The benefit of using the generic method is that
- // ASP.NET DI will attempt to inject the required
- // dependencies that are specified under the constructor
- // for us.
- .AddSingleton()
- .AddSingleton()
- .BuildServiceProvider();
-}
-public class CommandHandler
-{
- private readonly DiscordSocketClient _client;
- private readonly CommandService _commands;
- private readonly IServiceProvider _services;
-
- public CommandHandler(IServiceProvider services, CommandService commands, DiscordSocketClient client)
- {
- _commands = commands;
- _services = services;
- _client = client;
- }
-
- public async Task InitializeAsync()
- {
- // Pass the service provider to the second parameter of
- // AddModulesAsync to inject dependencies to all modules
- // that may require them.
- await _commands.AddModulesAsync(
- assembly: Assembly.GetEntryAssembly(),
- services: _services);
- _client.MessageReceived += HandleCommandAsync;
- }
-
- public async Task HandleCommandAsync(SocketMessage msg)
- {
- // ...
- // Pass the service provider to the ExecuteAsync method for
- // precondition checks.
- await _commands.ExecuteAsync(
- context: context,
- argPos: argPos,
- services: _services);
- // ...
- }
-}
diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs
deleted file mode 100644
index 3e42074ca..000000000
--- a/docs/guides/text_commands/samples/dependency-injection/dependency_module.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// After setting up dependency injection, modules will need to request
-// the dependencies to let the library know to pass
-// them along during execution.
-
-// Dependency can be injected in two ways with Discord.Net.
-// You may inject any required dependencies via...
-// the module constructor
-// -or-
-// public settable properties
-
-// Injection via constructor
-public class DatabaseModule : ModuleBase
-{
- private readonly DatabaseService _database;
- public DatabaseModule(DatabaseService database)
- {
- _database = database;
- }
-
- [Command("read")]
- public async Task ReadFromDbAsync()
- {
- await ReplyAsync(_database.GetData());
- }
-}
-
-// Injection via public settable properties
-public class DatabaseModule : ModuleBase
-{
- public DatabaseService DbService { get; set; }
-
- [Command("read")]
- public async Task ReadFromDbAsync()
- {
- await ReplyAsync(DbService.GetData());
- }
-}
diff --git a/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs b/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs
deleted file mode 100644
index 48cd52308..000000000
--- a/docs/guides/text_commands/samples/dependency-injection/dependency_module_noinject.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Sometimes injecting dependencies automatically with the provided
-// methods in the prior example may not be desired.
-
-// You may explicitly tell Discord.Net to **not** inject the properties
-// by either...
-// restricting the access modifier
-// -or-
-// applying DontInjectAttribute to the property
-
-// Restricting the access modifier of the property
-public class ImageModule : ModuleBase
-{
- public ImageService ImageService { get; }
- public ImageModule()
- {
- ImageService = new ImageService();
- }
-}
-
-// Applying DontInjectAttribute
-public class ImageModule : ModuleBase
-{
- [DontInject]
- public ImageService ImageService { get; set; }
- public ImageModule()
- {
- ImageService = new ImageService();
- }
-}
diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml
index f122ea6ba..c892eb8c4 100644
--- a/docs/guides/toc.yml
+++ b/docs/guides/toc.yml
@@ -6,9 +6,6 @@
items:
- name: Installation
topicUid: Guides.GettingStarted.Installation
- items:
- - name: Nightly builds
- topicUid: Guides.GettingStarted.Installation.Labs
- name: Your First Bot
topicUid: Guides.GettingStarted.FirstBot
- name: Terminology
@@ -29,6 +26,18 @@
topicUid: Guides.Entities.Casting
- name: Glossary & Flowcharts
topicUid: Guides.Entities.Glossary
+- name: Dependency Injection
+ items:
+ - name: Introduction
+ topicUid: Guides.DI.Intro
+ - name: Injection
+ topicUid: Guides.DI.Injection
+ - name: Command- & Interaction Services
+ topicUid: Guides.DI.Services
+ - name: Service Types
+ topicUid: Guides.DI.Dependencies
+ - name: Scaling your Application
+ topicUid: Guides.DI.Scaling
- name: Working with Text-based Commands
items:
- name: Introduction
@@ -39,8 +48,6 @@
topicUid: Guides.TextCommands.NamedArguments
- name: Preconditions
topicUid: Guides.TextCommands.Preconditions
- - name: Dependency Injection
- topicUid: Guides.TextCommands.DI
- name: Post-execution Handling
topicUid: Guides.TextCommands.PostExecution
- name: Working with the Interaction Framework
@@ -53,8 +60,6 @@
topicUid: Guides.IntFw.TypeConverters
- name: Preconditions
topicUid: Guides.IntFw.Preconditions
- - name: Dependency Injection
- topicUid: Guides.IntFw.DI
- name: Post-execution Handling
topicUid: Guides.IntFw.PostExecution
- name: Permissions
diff --git a/docs/guides/v2_v3_guide/v2_to_v3_guide.md b/docs/guides/v2_v3_guide/v2_to_v3_guide.md
index 6b4fa5282..a837f44d2 100644
--- a/docs/guides/v2_v3_guide/v2_to_v3_guide.md
+++ b/docs/guides/v2_v3_guide/v2_to_v3_guide.md
@@ -38,7 +38,7 @@ _client = new DiscordSocketClient(config);
This includes intents that receive messages such as: `GatewayIntents.GuildMessages, GatewayIntents.DirectMessages`
- GuildMembers: An intent disabled by default, as you need to enable it in the [developer portal].
- GuildPresences: Also disabled by default, this intent together with `GuildMembers` are the only intents not included in `AllUnprivileged`.
-- All: All intents, it is ill adviced to use this without care, as it _can_ cause a memory leak from presence.
+- All: All intents, it is ill advised to use this without care, as it _can_ cause a memory leak from presence.
The library will give responsive warnings if you specify unnecessary intents.
> [!NOTE]
diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md
index 555adbca2..36184e3a3 100644
--- a/docs/guides/voice/sending-voice.md
+++ b/docs/guides/voice/sending-voice.md
@@ -17,11 +17,9 @@ bot. (When developing on .NET Framework, this would be `bin/debug`,
when developing on .NET Core, this is where you execute `dotnet run`
from; typically the same directory as your csproj).
-For Windows Users, precompiled binaries are available for your
-convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).
+**For Windows users, precompiled binaries are available for your convienence [here](https://github.com/discord-net/Discord.Net/tree/dev/voice-natives).**
-For Linux Users, you will need to compile [Sodium] and [Opus] from
-source, or install them from your package manager.
+**For Linux users, you will need to compile [Sodium] and [Opus] from source, or install them from your package manager.**
[Sodium]: https://download.libsodium.org/libsodium/releases/
[Opus]: http://downloads.xiph.org/releases/opus/
diff --git a/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs b/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs
index 54b56cc60..54bc362ec 100644
--- a/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs
+++ b/experiment/Discord.Net.BuildOverrides/BuildOverrides.cs
@@ -251,7 +251,7 @@ namespace Discord
private static Assembly _overrideDomain_Resolving(AssemblyLoadContext arg1, AssemblyName arg2)
{
// resolve the override id
- var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.FirstOrDefault().FullName));
+ var v = _loadedOverrides.FirstOrDefault(x => x.Value.Any(x => x.Assembly.FullName == arg1.Assemblies.First().FullName));
return GetDependencyAsync(v.Key.Id, $"{arg2}").GetAwaiter().GetResult();
}
diff --git a/samples/ShardedClient/Services/InteractionHandlingService.cs b/samples/ShardedClient/Services/InteractionHandlingService.cs
index 3c41d7f33..fc2af8150 100644
--- a/samples/ShardedClient/Services/InteractionHandlingService.cs
+++ b/samples/ShardedClient/Services/InteractionHandlingService.cs
@@ -22,6 +22,7 @@ namespace ShardedClient.Services
_service.Log += LogAsync;
_client.InteractionCreated += OnInteractionAsync;
+ _client.ShardReady += ReadyAsync;
// For examples on how to handle post execution,
// see the InteractionFramework samples.
}
@@ -30,11 +31,6 @@ namespace ShardedClient.Services
public async Task InitializeAsync()
{
await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider);
-#if DEBUG
- await _service.RegisterCommandsToGuildAsync(1 /* implement */);
-#else
- await _service.RegisterCommandsGloballyAsync();
-#endif
}
private async Task OnInteractionAsync(SocketInteraction interaction)
@@ -53,5 +49,14 @@ namespace ShardedClient.Services
return Task.CompletedTask;
}
+
+ private async Task ReadyAsync(DiscordSocketClient _)
+ {
+#if DEBUG
+ await _service.RegisterCommandsToGuildAsync(1 /* implement */);
+#else
+ await _service.RegisterCommandsGloballyAsync();
+#endif
+ }
}
}
diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
index 22c58f5c7..f98c81abd 100644
--- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
@@ -206,6 +206,7 @@ namespace Discord.Commands
try
{
+ await instance.BeforeExecuteAsync(cmd).ConfigureAwait(false);
instance.BeforeExecute(cmd);
var task = method.Invoke(instance, args) as Task ?? Task.Delay(0);
@@ -221,6 +222,7 @@ namespace Discord.Commands
}
finally
{
+ await instance.AfterExecuteAsync(cmd).ConfigureAwait(false);
instance.AfterExecute(cmd);
(instance as IDisposable)?.Dispose();
}
diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs
index 8b021f4de..7a953b47b 100644
--- a/src/Discord.Net.Commands/IModuleBase.cs
+++ b/src/Discord.Net.Commands/IModuleBase.cs
@@ -1,4 +1,5 @@
using Discord.Commands.Builders;
+using System.Threading.Tasks;
namespace Discord.Commands
{
@@ -13,12 +14,24 @@ namespace Discord.Commands
/// The context to set.
void SetContext(ICommandContext context);
+ ///
+ /// Executed asynchronously before a command is run in this module base.
+ ///
+ /// The command thats about to run.
+ Task BeforeExecuteAsync(CommandInfo command);
+
///
/// Executed before a command is run in this module base.
///
/// The command thats about to run.
void BeforeExecute(CommandInfo command);
+ ///
+ /// Executed asynchronously after a command is run in this module base.
+ ///
+ /// The command thats about to run.
+ Task AfterExecuteAsync(CommandInfo command);
+
///
/// Executed after a command is ran in this module base.
///
diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs
index 5008cca35..b2d6ba119 100644
--- a/src/Discord.Net.Commands/ModuleBase.cs
+++ b/src/Discord.Net.Commands/ModuleBase.cs
@@ -46,6 +46,11 @@ namespace Discord.Commands
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options, allowedMentions, messageReference, components, stickers, embeds).ConfigureAwait(false);
}
///
+ /// The method to execute asynchronously before executing the command.
+ ///
+ /// The of the command to be executed.
+ protected virtual Task BeforeExecuteAsync(CommandInfo command) => Task.CompletedTask;
+ ///
/// The method to execute before executing the command.
///
/// The of the command to be executed.
@@ -53,6 +58,11 @@ namespace Discord.Commands
{
}
///
+ /// The method to execute asynchronously after executing the command.
+ ///
+ /// The of the command to be executed.
+ protected virtual Task AfterExecuteAsync(CommandInfo command) => Task.CompletedTask;
+ ///
/// The method to execute after executing the command.
///
/// The of the command to be executed.
@@ -76,7 +86,9 @@ namespace Discord.Commands
var newValue = context as T;
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}.");
}
+ Task IModuleBase.BeforeExecuteAsync(CommandInfo command) => BeforeExecuteAsync(command);
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
+ Task IModuleBase.AfterExecuteAsync(CommandInfo command) => AfterExecuteAsync(command);
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder);
#endregion
diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
index d921a2474..d75a4e29c 100644
--- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace Discord
@@ -25,6 +26,44 @@ namespace Discord
///
int? UserLimit { get; }
+ ///
+ /// Bulk-deletes multiple messages.
+ ///
+ ///
+ /// The following example gets 250 messages from the channel and deletes them.
+ ///
+ /// var messages = await voiceChannel.GetMessagesAsync(250).FlattenAsync();
+ /// await voiceChannel.DeleteMessagesAsync(messages);
+ ///
+ ///
+ ///
+ /// This method attempts to remove the messages specified in bulk.
+ ///
+ /// Due to the limitation set by Discord, this method can only remove messages that are posted within 14 days!
+ ///
+ ///
+ /// The messages to be bulk-deleted.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous bulk-removal operation.
+ ///
+ Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null);
+ ///
+ /// Bulk-deletes multiple messages.
+ ///
+ ///
+ /// This method attempts to remove the messages specified in bulk.
+ ///
+ /// Due to the limitation set by Discord, this method can only remove messages that are posted within 14 days!
+ ///
+ ///
+ /// The snowflake identifier of the messages to be bulk-deleted.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous bulk-removal operation.
+ ///
+ Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null);
+
///
/// Modifies this voice channel.
///
diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
index cb57b2726..52a70a6f5 100644
--- a/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/GuildFeature.cs
@@ -12,174 +12,174 @@ namespace Discord
///
/// The guild has no features.
///
- None = 0,
+ None = 0L,
///
/// The guild has access to animated banners.
///
- AnimatedBanner = 1 << 0,
+ AnimatedBanner = 1L << 0,
///
/// The guild has access to set an animated guild icon.
///
- AnimatedIcon = 1 << 1,
+ AnimatedIcon = 1L << 1,
///
/// The guild has access to set a guild banner image.
///
- Banner = 1 << 2,
+ Banner = 1L << 2,
///
/// The guild has access to channel banners.
///
- ChannelBanner = 1 << 3,
+ ChannelBanner = 1L << 3,
///
/// The guild has access to use commerce features (i.e. create store channels).
///
- Commerce = 1 << 4,
+ Commerce = 1L << 4,
///
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates.
///
- Community = 1 << 5,
+ Community = 1L << 5,
///
/// The guild is able to be discovered in the directory.
///
- Discoverable = 1 << 6,
+ Discoverable = 1L << 6,
///
/// The guild has discoverable disabled.
///
- DiscoverableDisabled = 1 << 7,
+ DiscoverableDisabled = 1L << 7,
///
/// The guild has enabled discoverable before.
///
- EnabledDiscoverableBefore = 1 << 8,
+ EnabledDiscoverableBefore = 1L << 8,
///
/// The guild is able to be featured in the directory.
///
- Featureable = 1 << 9,
+ Featureable = 1L << 9,
///
/// The guild has a force relay.
///
- ForceRelay = 1 << 10,
+ ForceRelay = 1L << 10,
///
/// The guild has a directory entry.
///
- HasDirectoryEntry = 1 << 11,
+ HasDirectoryEntry = 1L << 11,
///
/// The guild is a hub.
///
- Hub = 1 << 12,
+ Hub = 1L << 12,
///
/// You shouldn't be here...
///
- InternalEmployeeOnly = 1 << 13,
+ InternalEmployeeOnly = 1L << 13,
///
/// The guild has access to set an invite splash background.
///
- InviteSplash = 1 << 14,
+ InviteSplash = 1L << 14,
///
/// The guild is linked to a hub.
///
- LinkedToHub = 1 << 15,
+ LinkedToHub = 1L << 15,
///
/// The guild has member profiles.
///
- MemberProfiles = 1 << 16,
+ MemberProfiles = 1L << 16,
///
/// The guild has enabled Membership Screening.
///
- MemberVerificationGateEnabled = 1 << 17,
+ MemberVerificationGateEnabled = 1L << 17,
///
/// The guild has enabled monetization.
///
- MonetizationEnabled = 1 << 18,
+ MonetizationEnabled = 1L << 18,
///
/// The guild has more emojis.
///
- MoreEmoji = 1 << 19,
+ MoreEmoji = 1L << 19,
///
/// The guild has increased custom sticker slots.
///
- MoreStickers = 1 << 20,
+ MoreStickers = 1L << 20,
///
/// The guild has access to create news channels.
///
- News = 1 << 21,
+ News = 1L << 21,
///
/// The guild has new thread permissions.
///
- NewThreadPermissions = 1 << 22,
+ NewThreadPermissions = 1L << 22,
///
/// The guild is partnered.
///
- Partnered = 1 << 23,
+ Partnered = 1L << 23,
///
/// The guild has a premium tier three override; guilds made by Discord usually have this.
///
- PremiumTier3Override = 1 << 24,
+ PremiumTier3Override = 1L << 24,
///
/// The guild can be previewed before joining via Membership Screening or the directory.
///
- PreviewEnabled = 1 << 25,
+ PreviewEnabled = 1L << 25,
///
/// The guild has access to create private threads.
///
- PrivateThreads = 1 << 26,
+ PrivateThreads = 1L << 26,
///
/// The guild has relay enabled.
///
- RelayEnabled = 1 << 27,
+ RelayEnabled = 1L << 27,
///
/// The guild is able to set role icons.
///
- RoleIcons = 1 << 28,
+ RoleIcons = 1L << 28,
///
/// The guild has role subscriptions available for purchase.
///
- RoleSubscriptionsAvailableForPurchase = 1 << 29,
+ RoleSubscriptionsAvailableForPurchase = 1L << 29,
///
/// The guild has role subscriptions enabled.
///
- RoleSubscriptionsEnabled = 1 << 30,
+ RoleSubscriptionsEnabled = 1L << 30,
///
/// The guild has access to the seven day archive time for threads.
///
- SevenDayThreadArchive = 1 << 31,
+ SevenDayThreadArchive = 1L << 31,
///
/// The guild has text in voice enabled.
///
- TextInVoiceEnabled = 1 << 32,
+ TextInVoiceEnabled = 1L << 32,
///
/// The guild has threads enabled.
///
- ThreadsEnabled = 1 << 33,
+ ThreadsEnabled = 1L << 33,
///
/// The guild has testing threads enabled.
///
- ThreadsEnabledTesting = 1 << 34,
+ ThreadsEnabledTesting = 1L << 34,
///
/// The guild has the default thread auto archive.
///
- ThreadsDefaultAutoArchiveDuration = 1 << 35,
+ ThreadsDefaultAutoArchiveDuration = 1L << 35,
///
/// The guild has access to the three day archive time for threads.
///
- ThreeDayThreadArchive = 1 << 36,
+ ThreeDayThreadArchive = 1L << 36,
///
/// The guild has enabled ticketed events.
///
- TicketedEventsEnabled = 1 << 37,
+ TicketedEventsEnabled = 1L << 37,
///
/// The guild has access to set a vanity URL.
///
- VanityUrl = 1 << 38,
+ VanityUrl = 1L << 38,
///
/// The guild is verified.
///
- Verified = 1 << 39,
+ Verified = 1L << 39,
///
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
///
- VIPRegions = 1 << 40,
+ VIPRegions = 1L << 40,
///
/// The guild has enabled the welcome screen.
///
- WelcomeScreenEnabled = 1 << 41,
+ WelcomeScreenEnabled = 1L << 41,
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
index 556257b47..bceefda32 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
@@ -72,6 +72,16 @@ namespace Discord
///
public double? MaxValue { get; set; }
+ ///
+ /// Gets or sets the minimum allowed length for a string input.
+ ///
+ public int? MinLength { get; set; }
+
+ ///
+ /// Gets or sets the maximum allowed length for a string input.
+ ///
+ public int? MaxLength { get; set; }
+
///
/// Gets or sets the choices for string and int types for the user to pick from.
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
index 7e8c93339..fb179b661 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
@@ -47,6 +47,16 @@ namespace Discord
///
double? MaxValue { get; }
+ ///
+ /// Gets the minimum allowed length for a string input.
+ ///
+ int? MinLength { get; }
+
+ ///
+ /// Gets the maximum allowed length for a string input.
+ ///
+ int? MaxLength { get; }
+
///
/// Gets the choices for string and int types for the user to pick from.
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index 37342b039..fd8798ed3 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -1198,6 +1198,10 @@ namespace Discord
public class TextInputBuilder
{
+ ///
+ /// The max length of a .
+ ///
+ public const int MaxPlaceholderLength = 100;
public const int LargestMaxLength = 4000;
///
@@ -1229,13 +1233,13 @@ namespace Discord
///
/// Gets or sets the placeholder of the current text input.
///
- /// is longer than 100 characters
+ /// is longer than characters
public string Placeholder
{
get => _placeholder;
- set => _placeholder = (value?.Length ?? 0) <= 100
+ set => _placeholder = (value?.Length ?? 0) <= MaxPlaceholderLength
? value
- : throw new ArgumentException("Placeholder cannot have more than 100 characters.");
+ : throw new ArgumentException($"Placeholder cannot have more than {MaxPlaceholderLength} characters.");
}
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
index b2f00d4f6..5b36612de 100644
--- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
@@ -202,7 +202,8 @@ namespace Discord
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List options = null, List channelTypes = null, IDictionary nameLocalizations = null,
- IDictionary descriptionLocalizations = null, params ApplicationCommandOptionChoiceProperties[] choices)
+ IDictionary descriptionLocalizations = null,
+ int? minLength = null, int? maxLength = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
Preconditions.Options(name, description);
@@ -227,7 +228,9 @@ namespace Discord
Choices = (choices ?? Array.Empty()).ToList(),
ChannelTypes = channelTypes,
MinValue = minValue,
- MaxValue = maxValue
+ MaxValue = maxValue,
+ MinLength = minLength,
+ MaxLength = maxLength,
};
if (nameLocalizations is not null)
@@ -474,6 +477,16 @@ namespace Discord
///
public double? MaxValue { get; set; }
+ ///
+ /// Gets or sets the minimum allowed length for a string input.
+ ///
+ public int? MinLength { get; set; }
+
+ ///
+ /// Gets or sets the maximum allowed length for a string input.
+ ///
+ public int? MaxLength { get; set; }
+
///
/// Gets or sets the choices for string and int types for the user to pick from.
///
@@ -507,6 +520,7 @@ namespace Discord
{
bool isSubType = Type == ApplicationCommandOptionType.SubCommandGroup;
bool isIntType = Type == ApplicationCommandOptionType.Integer;
+ bool isStrType = Type == ApplicationCommandOptionType.String;
if (isSubType && (Options == null || !Options.Any()))
throw new InvalidOperationException("SubCommands/SubCommandGroups must have at least one option");
@@ -520,6 +534,12 @@ namespace Discord
if (isIntType && MaxValue != null && MaxValue % 1 != 0)
throw new InvalidOperationException("MaxValue cannot have decimals on Integer command options.");
+ if(isStrType && MinLength is not null && MinLength < 0)
+ throw new InvalidOperationException("MinLength cannot be smaller than 0.");
+
+ if (isStrType && MaxLength is not null && MaxLength < 1)
+ throw new InvalidOperationException("MaxLength cannot be smaller than 1.");
+
return new ApplicationCommandOptionProperties
{
Name = Name,
@@ -537,6 +557,8 @@ namespace Discord
MaxValue = MaxValue,
NameLocalizations = _nameLocalizations,
DescriptionLocalizations = _descriptionLocalizations
+ MinLength = MinLength,
+ MaxLength = MaxLength,
};
}
@@ -560,7 +582,8 @@ namespace Discord
public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type,
string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
List options = null, List channelTypes = null, IDictionary nameLocalizations = null,
- IDictionary descriptionLocalizations = null, params ApplicationCommandOptionChoiceProperties[] choices)
+ IDictionary descriptionLocalizations = null,
+ int? minLength = null, int? maxLength = null, params ApplicationCommandOptionChoiceProperties[] choices)
{
Preconditions.Options(name, description);
@@ -582,6 +605,8 @@ namespace Discord
IsAutocomplete = isAutocomplete,
MinValue = minValue,
MaxValue = maxValue,
+ MinLength = minLength,
+ MaxLength = maxLength,
Options = options,
Type = type,
Choices = (choices ?? Array.Empty()).ToList(),
@@ -816,6 +841,28 @@ namespace Discord
return this;
}
+ ///
+ /// Sets the current builders min length field.
+ ///
+ /// The value to set.
+ /// The current builder.
+ public SlashCommandOptionBuilder WithMinLength(int length)
+ {
+ MinLength = length;
+ return this;
+ }
+
+ ///
+ /// Sets the current builders max length field.
+ ///
+ /// The value to set.
+ /// The current builder.
+ public SlashCommandOptionBuilder WithMaxLength(int lenght)
+ {
+ MaxLength = lenght;
+ return this;
+ }
+
///
/// Sets the current type of this builder.
///
diff --git a/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs b/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs
new file mode 100644
index 000000000..1099e7d92
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/MaxLengthAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Discord.Interactions
+{
+ ///
+ /// Sets the maximum length allowed for a string type parameter.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+ public class MaxLengthAttribute : Attribute
+ {
+ ///
+ /// Gets the maximum length allowed for a string type parameter.
+ ///
+ public int Length { get; }
+
+ ///
+ /// Sets the maximum length allowed for a string type parameter.
+ ///
+ /// Maximum string length allowed.
+ public MaxLengthAttribute(int lenght)
+ {
+ Length = lenght;
+ }
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs b/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs
new file mode 100644
index 000000000..7d0b0fd63
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/MinLengthAttribute.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Discord.Interactions
+{
+ ///
+ /// Sets the minimum length allowed for a string type parameter.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+ public class MinLengthAttribute : Attribute
+ {
+ ///
+ /// Gets the minimum length allowed for a string type parameter.
+ ///
+ public int Length { get; }
+
+ ///
+ /// Sets the minimum length allowed for a string type parameter.
+ ///
+ /// Minimum string length allowed.
+ public MinLengthAttribute(int lenght)
+ {
+ Length = lenght;
+ }
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs
index 9d1cee8d9..057055ffc 100644
--- a/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs
+++ b/src/Discord.Net.Interactions/Attributes/Preconditions/RequireContextAttribute.cs
@@ -58,7 +58,7 @@ namespace Discord.Interactions
if ((Contexts & ContextType.Guild) != 0)
isValid = !context.Interaction.IsDMInteraction;
- if ((Contexts & ContextType.DM) != 0 && (Contexts & ContextType.Group) != 0)
+ if ((Contexts & ContextType.DM) != 0)
isValid = context.Interaction.IsDMInteraction;
if (isValid)
diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
index 1bbdfcc4a..35126a674 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
@@ -463,6 +463,12 @@ namespace Discord.Interactions.Builders
case MinValueAttribute minValue:
builder.MinValue = minValue.Value;
break;
+ case MinLengthAttribute minLength:
+ builder.MinLength = minLength.Length;
+ break;
+ case MaxLengthAttribute maxLength:
+ builder.MaxLength = maxLength.Length;
+ break;
case ComplexParameterAttribute complexParameter:
{
builder.IsComplexParameter = true;
diff --git a/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs
index d600c9cc7..6f8038cef 100644
--- a/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Parameters/SlashCommandParameterBuilder.cs
@@ -28,6 +28,16 @@ namespace Discord.Interactions.Builders
///
public double? MinValue { get; set; }
+ ///
+ /// Gets or sets the minimum length allowed for a string type parameter.
+ ///
+ public int? MinLength { get; set; }
+
+ ///
+ /// Gets or sets the maximum length allowed for a string type parameter.
+ ///
+ public int? MaxLength { get; set; }
+
///
/// Gets a collection of the choices of this command.
///
@@ -125,6 +135,32 @@ namespace Discord.Interactions.Builders
return this;
}
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public SlashCommandParameterBuilder WithMinLength(int length)
+ {
+ MinLength = length;
+ return this;
+ }
+
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public SlashCommandParameterBuilder WithMaxLength(int length)
+ {
+ MaxLength = length;
+ return this;
+ }
+
///
/// Adds parameter choices to .
///
diff --git a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
index 8f0987661..d970b9930 100644
--- a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
+++ b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
@@ -19,9 +19,36 @@ namespace Discord.Interactions
if (!ModalUtils.TryGet(out var modalInfo))
throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}");
+ await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal);
+ }
+
+ ///
+ /// Respond to an interaction with a .
+ ///
+ ///
+ /// This method overload uses the parameter to create a new
+ /// if there isn't a built one already in cache.
+ ///
+ /// Type of the implementation.
+ /// The interaction to respond to.
+ /// Interaction service instance that should be used to build s.
+ /// The request options for this request.
+ /// Delegate that can be used to modify the modal.
+ ///
+ public static async Task RespondWithModalAsync(this IDiscordInteraction interaction, string customId, InteractionService interactionService,
+ RequestOptions options = null, Action modifyModal = null)
+ where T : class, IModal
+ {
+ var modalInfo = ModalUtils.GetOrAdd(interactionService);
+
+ await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal);
+ }
+
+ private static async Task SendModalResponseAsync(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, RequestOptions options = null, Action modifyModal = null)
+ {
var builder = new ModalBuilder(modalInfo.Title, customId);
- foreach(var input in modalInfo.Components)
+ foreach (var input in modalInfo.Components)
switch (input)
{
case TextInputComponentInfo textComponent:
diff --git a/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs b/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs
index 8702d69f7..0bce42186 100644
--- a/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs
+++ b/src/Discord.Net.Interactions/Info/Parameters/SlashCommandParameterInfo.cs
@@ -38,6 +38,16 @@ namespace Discord.Interactions
///
public double? MaxValue { get; }
+ ///
+ /// Gets the minimum length allowed for a string type parameter.
+ ///
+ public int? MinLength { get; }
+
+ ///
+ /// Gets the maximum length allowed for a string type parameter.
+ ///
+ public int? MaxLength { get; }
+
///
/// Gets the that will be used to convert the incoming into
/// .
@@ -86,6 +96,8 @@ namespace Discord.Interactions
Description = builder.Description;
MaxValue = builder.MaxValue;
MinValue = builder.MinValue;
+ MinLength = builder.MinLength;
+ MaxLength = builder.MaxLength;
IsComplexParameter = builder.IsComplexParameter;
IsAutocomplete = builder.Autocomplete;
Choices = builder.Choices.ToImmutableArray();
diff --git a/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs
index 874171175..d85b376d1 100644
--- a/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs
+++ b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs
@@ -9,10 +9,11 @@ namespace Discord.Interactions
public NullableConverter(InteractionService interactionService, IServiceProvider services)
{
- var type = Nullable.GetUnderlyingType(typeof(T));
+ var nullableType = typeof(T);
+ var type = Nullable.GetUnderlyingType(nullableType);
if (type is null)
- throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type");
+ throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {nullableType.FullName}", nameof(type));
_typeConverter = interactionService.GetTypeConverter(type, services);
}
diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
index 97a0b1f08..1ed867a55 100644
--- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
+++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs
@@ -31,6 +31,8 @@ namespace Discord.Interactions
MinValue = parameterInfo.MinValue,
NameLocalizations = localizationManager?.GetAllNames(parameterPath, LocalizationTarget.Parameter) ?? ImmutableDictionary.Empty,
DescriptionLocalizations = localizationManager?.GetAllDescriptions(parameterPath, LocalizationTarget.Parameter) ?? ImmutableDictionary.Empty
+ MinLength = parameterInfo.MinLength,
+ MaxLength = parameterInfo.MaxLength,
};
parameterInfo.TypeConverter.Write(props, parameterInfo);
@@ -268,6 +270,12 @@ namespace Discord.Interactions
Options = commandOption.Options?.Select(x => x.ToApplicationCommandOptionProps()).ToList(),
NameLocalizations = commandOption.NameLocalizations?.ToImmutableDictionary(),
DescriptionLocalizations = commandOption.DescriptionLocalizations?.ToImmutableDictionary()
+ MaxLength = commandOption.MaxLength,
+ MinLength = commandOption.MinLength,
+ MaxValue = commandOption.MaxValue,
+ MinValue = commandOption.MinValue,
+ IsAutocomplete = commandOption.IsAutocomplete.GetValueOrDefault(),
+ ChannelTypes = commandOption.ChannelTypes.ToList(),
};
public static Modal ToModal(this ModalInfo modalInfo, string customId, Action modifyModal = null)
diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs
index 0ec2025fa..fb64d5ebe 100644
--- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs
+++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs
@@ -51,6 +51,12 @@ namespace Discord.API
[JsonProperty("description_localized")]
public Optional DescriptionLocalized { get; set; }
+ [JsonProperty("min_length")]
+ public Optional MinLength { get; set; }
+
+ [JsonProperty("max_length")]
+ public Optional MaxLength { get; set; }
+
public ApplicationCommandOption() { }
public ApplicationCommandOption(IApplicationCommandOption cmd)
@@ -69,6 +75,8 @@ namespace Discord.API
Default = cmd.IsDefault ?? Optional.Unspecified;
MinValue = cmd.MinValue ?? Optional.Unspecified;
MaxValue = cmd.MaxValue ?? Optional.Unspecified;
+ MinLength = cmd.MinLength ?? Optional.Unspecified;
+ MaxLength = cmd.MaxLength ?? Optional.Unspecified;
Autocomplete = cmd.IsAutocomplete ?? Optional.Unspecified;
Name = cmd.Name;
@@ -95,6 +103,8 @@ namespace Discord.API
Default = option.IsDefault ?? Optional.Unspecified;
MinValue = option.MinValue ?? Optional.Unspecified;
MaxValue = option.MaxValue ?? Optional.Unspecified;
+ MinLength = option.MinLength ?? Optional.Unspecified;
+ MaxLength = option.MaxLength ?? Optional.Unspecified;
ChannelTypes = option.ChannelTypes?.ToArray() ?? Optional.Unspecified;
diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs
index adc93999e..686c7b030 100644
--- a/src/Discord.Net.Rest/BaseDiscordClient.cs
+++ b/src/Discord.Net.Rest/BaseDiscordClient.cs
@@ -57,7 +57,7 @@ namespace Discord.Rest
if (info == null)
await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false);
else
- await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false);
+ await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} Remaining: {info.Value.RetryAfter}s {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false);
};
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
}
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index faff1541e..0559f757f 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -1777,7 +1777,7 @@ namespace Discord.API
if (args.TargetType.Value == TargetUserType.Stream)
Preconditions.GreaterThan(args.TargetUserId, 0, nameof(args.TargetUserId));
if (args.TargetType.Value == TargetUserType.EmbeddedApplication)
- Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetUserId));
+ Preconditions.GreaterThan(args.TargetApplicationId, 0, nameof(args.TargetApplicationId));
}
options = RequestOptions.CreateOrClone(options);
diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs
index 2b9b95418..8eb03114d 100644
--- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs
+++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ThreadUpdateAuditLogData.cs
@@ -15,7 +15,7 @@ namespace Discord.Rest
Thread = thread;
ThreadType = type;
Before = before;
- After = After;
+ After = after;
}
internal static ThreadUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry)
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 1c340faed..1dbbd4bd6 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -428,7 +428,7 @@ namespace Discord.Rest
var ids = args.Roles.Value.Select(r => r.Id);
if (args.RoleIds.IsSpecified)
- args.RoleIds.Value.Concat(ids);
+ args.RoleIds = Optional.Create(args.RoleIds.Value.Concat(ids));
else
args.RoleIds = Optional.Create(ids);
}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index a7e6c0d23..eb3254619 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -1408,7 +1408,7 @@ namespace Discord.Rest
///
/// The user to disconnect.
/// A task that represents the asynchronous operation for disconnecting a user.
- async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional());
+ async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = null);
///
async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
index 22e56a733..102ede7b7 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
@@ -49,6 +49,9 @@ namespace Discord.Rest
internal override async Task UpdateAsync(DiscordRestClient client, Model model, bool doApiCall)
{
await base.UpdateAsync(client, model, doApiCall).ConfigureAwait(false);
+
+ if (model.Data.IsSpecified && model.Data.Value is RestCommandBaseData data)
+ Data = data;
}
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
index 751b0c213..3ac15e695 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
@@ -35,6 +35,12 @@ namespace Discord.Rest
///
public double? MaxValue { get; private set; }
+ ///
+ public int? MinLength { get; private set; }
+
+ ///
+ public int? MaxLength { get; private set; }
+
///
/// Gets a collection of s for this command.
///
@@ -104,6 +110,9 @@ namespace Discord.Rest
if (model.Autocomplete.IsSpecified)
IsAutocomplete = model.Autocomplete.Value;
+ MinLength = model.MinLength.ToNullable();
+ MaxLength = model.MaxLength.ToNullable();
+
Options = model.Options.IsSpecified
? model.Options.Value.Select(Create).ToImmutableArray()
: ImmutableArray.Create();
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
index 43d13f521..ba2de12a9 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs
@@ -426,7 +426,7 @@ namespace Discord.Rest
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options)
=> await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
///
- async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string text, string fileName, Embed[] embeds, bool isTTS, bool ephemeral,
+ async Task IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral,
AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options)
=> await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false);
///
diff --git a/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs
new file mode 100644
index 000000000..5555dc842
--- /dev/null
+++ b/src/Discord.Net.WebSocket/API/Gateway/WebhooksUpdatedEvent.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace Discord.API.Gateway
+{
+ internal class WebhooksUpdatedEvent
+ {
+ [JsonProperty("guild_id")]
+ public ulong GuildId { get; set; }
+
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+ }
+}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index c47591418..fb2110399 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -55,7 +55,7 @@ namespace Discord.WebSocket
/// Fired when a channel is updated.
///
///
- /// This event is fired when a generic channel has been destroyed. The event handler must return a
+ /// This event is fired when a generic channel has been updated. The event handler must return a
/// and accept 2 as its parameters.
///
///
@@ -106,7 +106,7 @@ namespace Discord.WebSocket
///
///
/// This event is fired when a message is deleted. The event handler must return a
- /// and accept a and
+ /// and accept a and
/// as its parameters.
///
///
@@ -117,11 +117,11 @@ namespace Discord.WebSocket
///
/// If caching is enabled via , the
/// entity will contain the deleted message; otherwise, in event
- /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
+ /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
/// .
///
///
- /// The source channel of the removed message will be passed into the
+ /// The source channel of the removed message will be passed into the
/// parameter.
///
///
@@ -143,7 +143,7 @@ namespace Discord.WebSocket
///
///
/// This event is fired when multiple messages are bulk deleted. The event handler must return a
- /// and accept an and
+ /// and accept an and
/// as its parameters.
///
///
@@ -154,11 +154,11 @@ namespace Discord.WebSocket
///
/// If caching is enabled via , the
/// entity will contain the deleted message; otherwise, in event
- /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
+ /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
/// .
///
///
- /// The source channel of the removed message will be passed into the
+ /// The source channel of the removed message will be passed into the
/// parameter.
///
///
@@ -178,14 +178,14 @@ namespace Discord.WebSocket
///
/// If caching is enabled via , the
/// entity will contain the original message; otherwise, in event
- /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
+ /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
/// .
///
///
/// The updated message will be passed into the parameter.
///
///
- /// The source channel of the updated message will be passed into the
+ /// The source channel of the updated message will be passed into the
/// parameter.
///
///
@@ -199,24 +199,24 @@ namespace Discord.WebSocket
///
///
/// This event is fired when a reaction is added to a user message. The event handler must return a
- /// and accept a , an
+ /// and accept a , an
/// , and a as its parameter.
///
///
/// If caching is enabled via , the
/// entity will contain the original message; otherwise, in event
- /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
+ /// that the message cannot be retrieved, the snowflake ID of the message is preserved in the
/// .
///
///
- /// The source channel of the reaction addition will be passed into the
+ /// The source channel of the reaction addition will be passed into the
/// parameter.
///
///
/// The reaction that was added will be passed into the parameter.
///
///
- /// When fetching the reaction from this event, a user may not be provided under
+ /// When fetching the reaction from this event, a user may not be provided under
/// . Please see the documentation of the property for more
/// information.
///
@@ -367,7 +367,7 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent, SocketGuildEvent, Task>>();
-
+
///
/// Fired when a guild event is cancelled.
///
@@ -877,5 +877,20 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent> _guildStickerDeleted = new AsyncEvent>();
#endregion
+
+ #region Webhooks
+
+ ///
+ /// Fired when a webhook is modified, moved, or deleted. If the webhook was
+ /// moved the channel represents the destination channel, not the source.
+ ///
+ public event Func WebhooksUpdated
+ {
+ add { _webhooksUpdated.Add(value); }
+ remove { _webhooksUpdated.Remove(value); }
+ }
+ internal readonly AsyncEvent> _webhooksUpdated = new AsyncEvent>();
+
+ #endregion
}
}
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
index 3a14692e0..9fc717762 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -496,6 +496,8 @@ namespace Discord.WebSocket
client.GuildScheduledEventStarted += (arg) => _guildScheduledEventStarted.InvokeAsync(arg);
client.GuildScheduledEventUserAdd += (arg1, arg2) => _guildScheduledEventUserAdd.InvokeAsync(arg1, arg2);
client.GuildScheduledEventUserRemove += (arg1, arg2) => _guildScheduledEventUserRemove.InvokeAsync(arg1, arg2);
+
+ client.WebhooksUpdated += (arg1, arg2) => _webhooksUpdated.InvokeAsync(arg1, arg2);
}
#endregion
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 55426234b..670ed4567 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -2320,7 +2320,7 @@ namespace Discord.WebSocket
case "INTERACTION_CREATE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false);
-
+
var data = (payload as JToken).ToObject(_serializer);
var guild = data.GuildId.IsSpecified ? GetGuild(data.GuildId.Value) : null;
@@ -2328,7 +2328,6 @@ namespace Discord.WebSocket
if (guild != null && !guild.IsSynced)
{
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false);
- return;
}
SocketUser user = data.User.IsSpecified
@@ -2348,15 +2347,8 @@ namespace Discord.WebSocket
{
channel = CreateDMChannel(data.ChannelId.Value, user, State);
}
- else
- {
- if (guild != null) // The guild id is set, but the guild cannot be found as the bot scope is not set.
- {
- await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false);
- return;
- }
- // The channel isnt required when responding to an interaction, so we can leave the channel null.
- }
+
+ // The channel isnt required when responding to an interaction, so we can leave the channel null.
}
}
else if (data.User.IsSpecified)
@@ -2841,6 +2833,23 @@ namespace Discord.WebSocket
#endregion
+ #region Webhooks
+
+ case "WEBHOOKS_UPDATE":
+ {
+ var data = (payload as JToken).ToObject(_serializer);
+ type = "WEBHOOKS_UPDATE";
+ await _gatewayLogger.DebugAsync("Received Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false);
+
+ var guild = State.GetGuild(data.GuildId);
+ var channel = State.GetChannel(data.ChannelId);
+
+ await TimedInvokeAsync(_webhooksUpdated, nameof(WebhooksUpdated), guild, channel);
+ }
+ break;
+
+ #endregion
+
#region Ignored (User only)
case "CHANNEL_PINS_ACK":
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false);
@@ -2860,9 +2869,6 @@ namespace Discord.WebSocket
case "USER_SETTINGS_UPDATE":
await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false);
break;
- case "WEBHOOKS_UPDATE":
- await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false);
- break;
#endregion
#region Others
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs
index 43f23de1a..42f0c76d4 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs
@@ -39,7 +39,7 @@ namespace Discord.WebSocket
}
internal new static SocketCategoryChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketCategoryChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketCategoryChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
index bc6e28442..ea58ecdb5 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketForumChannel.cs
@@ -34,7 +34,7 @@ namespace Discord.WebSocket
internal new static SocketForumChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketForumChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketForumChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
index 16ed7b32d..808982785 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs
@@ -36,8 +36,8 @@ namespace Discord.WebSocket
/// Gets a collection of users that are able to view the channel.
///
///
- /// If this channel is a voice channel, a collection of users who are currently connected to this channel
- /// is returned.
+ /// If this channel is a voice channel, use to retrieve a
+ /// collection of users who are currently connected to this channel.
///
///
/// A read-only collection of users that can access the channel (i.e. the users seen in the user list).
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs
index eed8f9374..81e152530 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketNewsChannel.cs
@@ -23,7 +23,7 @@ namespace Discord.WebSocket
}
internal new static SocketNewsChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketNewsChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketNewsChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
index 56cd92185..4983bc466 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketStageChannel.cs
@@ -49,7 +49,7 @@ namespace Discord.WebSocket
internal new static SocketStageChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketStageChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketStageChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index 6aece7d78..2d8aeeae7 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -61,12 +61,12 @@ namespace Discord.WebSocket
internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild)
: base(discord, id, guild)
{
- if (Discord.MessageCacheSize > 0)
+ if (Discord?.MessageCacheSize > 0)
_messages = new MessageCache(Discord);
}
internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketTextChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketTextChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
index 7bf65d638..9036659fe 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs
@@ -50,7 +50,7 @@ namespace Discord.WebSocket
}
internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model)
{
- var entity = new SocketVoiceChannel(guild.Discord, model.Id, guild);
+ var entity = new SocketVoiceChannel(guild?.Discord, model.Id, guild);
entity.Update(state, model);
return entity;
}
@@ -58,8 +58,8 @@ namespace Discord.WebSocket
internal override void Update(ClientState state, Model model)
{
base.Update(state, model);
- Bitrate = model.Bitrate.Value;
- UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null;
+ Bitrate = model.Bitrate.GetValueOrDefault(64000);
+ UserLimit = model.UserLimit.GetValueOrDefault() != 0 ? model.UserLimit.Value : (int?)null;
RTCRegion = model.RTCRegion.GetValueOrDefault(null);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index a8535ca1d..55f098b2f 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -532,13 +532,10 @@ namespace Discord.WebSocket
Features = model.Features;
var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05));
- if (model.Roles != null)
+ for (int i = 0; i < model.Roles.Length; i++)
{
- for (int i = 0; i < model.Roles.Length; i++)
- {
- var role = SocketRole.Create(this, state, model.Roles[i]);
- roles.TryAdd(role.Id, role);
- }
+ var role = SocketRole.Create(this, state, model.Roles[i]);
+ roles.TryAdd(role.Id, role);
}
_roles = roles;
@@ -1413,7 +1410,7 @@ namespace Discord.WebSocket
///
/// The user to disconnect.
/// A task that represents the asynchronous operation for disconnecting a user.
- async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = new Optional());
+ async Task IGuild.DisconnectAsync(IGuildUser user) => await user.ModifyAsync(x => x.Channel = null);
#endregion
#region Stickers
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs
index ad5575caa..0c473bcdd 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/MessageCommands/SocketMessageCommand.cs
@@ -20,9 +20,7 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: null;
- ulong? guildId = null;
- if (Channel is SocketGuildChannel guildChannel)
- guildId = guildChannel.Guild.Id;
+ ulong? guildId = model.GuildId.ToNullable();
Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs
index c33c06f83..70e06f273 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/ContextMenuCommands/UserCommands/SocketUserCommand.cs
@@ -20,9 +20,7 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: null;
- ulong? guildId = null;
- if (Channel is SocketGuildChannel guildChannel)
- guildId = guildChannel.Guild.Id;
+ ulong? guildId = model.GuildId.ToNullable();
Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
index 4f9a769c2..2a1a67d04 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
@@ -61,7 +61,9 @@ namespace Discord.WebSocket
author = channel.Guild.GetUser(model.Message.Value.Author.Value.Id);
}
else if (model.Message.Value.Author.IsSpecified)
- author = (Channel as SocketChannel).GetUser(model.Message.Value.Author.Value.Id);
+ author = (Channel as SocketChannel)?.GetUser(model.Message.Value.Author.Value.Id);
+
+ author ??= Discord.State.GetOrAddUser(model.Message.Value.Author.Value.Id, _ => SocketGlobalUser.Create(Discord, Discord.State, model.Message.Value.Author.Value));
Message = SocketUserMessage.Create(Discord, Discord.State, author, Channel, model.Message.Value);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs
index b3aa4a826..69f733e85 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketSlashCommand.cs
@@ -20,9 +20,7 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: null;
- ulong? guildId = null;
- if (Channel is SocketGuildChannel guildChannel)
- guildId = guildChannel.Guild.Id;
+ ulong? guildId = model.GuildId.ToNullable();
Data = SocketSlashCommandData.Create(client, dataModel, guildId);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
index 8c0fca90b..b0ddd0012 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs
@@ -19,7 +19,7 @@ namespace Discord.WebSocket
/// Gets whether or not this command is a global application command.
///
public bool IsGlobalCommand
- => Guild == null;
+ => GuildId is null;
///
public ulong ApplicationId { get; private set; }
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs
index a20b4e906..78bb45141 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs
@@ -33,6 +33,12 @@ namespace Discord.WebSocket
///
public double? MaxValue { get; private set; }
+ ///
+ public int? MinLength { get; private set; }
+
+ ///
+ public int? MaxLength { get; private set; }
+
///
/// Gets a collection of choices for the user to pick from.
///
@@ -98,6 +104,9 @@ namespace Discord.WebSocket
IsAutocomplete = model.Autocomplete.ToNullable();
+ MinLength = model.MinLength.ToNullable();
+ MaxLength = model.MaxLength.ToNullable();
+
Choices = model.Choices.IsSpecified
? model.Choices.Value.Select(SocketApplicationCommandChoice.Create).ToImmutableArray()
: ImmutableArray.Create();
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
index 273f27c9c..bdab128f4 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
@@ -43,9 +43,7 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: null;
- ulong? guildId = null;
- if (Channel is SocketGuildChannel guildChannel)
- guildId = guildChannel.Guild.Id;
+ ulong? guildId = model.GuildId.ToNullable();
Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
index a629fd069..2167a69a1 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs
@@ -1,3 +1,4 @@
+using Discord.Net;
using System.Collections.Generic;
namespace Discord.WebSocket
@@ -39,19 +40,30 @@ namespace Discord.WebSocket
{
foreach (var channel in resolved.Channels.Value)
{
- SocketChannel socketChannel = guild != null
+ var socketChannel = guild != null
? guild.GetChannel(channel.Value.Id)
: discord.GetChannel(channel.Value.Id);
if (socketChannel == null)
{
- var channelModel = guild != null
- ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult()
- : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult();
-
- socketChannel = guild != null
- ? SocketGuildChannel.Create(guild, discord.State, channelModel)
- : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel);
+ try
+ {
+ var channelModel = guild != null
+ ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id)
+ .ConfigureAwait(false).GetAwaiter().GetResult()
+ : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false)
+ .GetAwaiter().GetResult();
+
+ socketChannel = guild != null
+ ? SocketGuildChannel.Create(guild, discord.State, channelModel)
+ : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel);
+ }
+ catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.MissingPermissions)
+ {
+ socketChannel = guildId != null
+ ? SocketGuildChannel.Create(guild, discord.State, channel.Value)
+ : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channel.Value);
+ }
}
discord.State.AddChannel(socketChannel);
@@ -69,11 +81,14 @@ namespace Discord.WebSocket
}
}
- if (resolved.Roles.IsSpecified)
+ if (resolved.Roles.IsSpecified && guild != null)
{
foreach (var role in resolved.Roles.Value)
{
- var socketRole = guild.AddOrUpdateRole(role.Value);
+ var socketRole = guild is null
+ ? SocketRole.Create(null, discord.State, role.Value)
+ : guild.AddOrUpdateRole(role.Value);
+
Roles.Add(ulong.Parse(role.Key), socketRole);
}
}
@@ -93,16 +108,19 @@ namespace Discord.WebSocket
author = guild.GetUser(msg.Value.Author.Value.Id);
}
else
- author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id);
+ author = (channel as SocketChannel)?.GetUser(msg.Value.Author.Value.Id);
if (channel == null)
{
- if (!msg.Value.GuildId.IsSpecified) // assume it is a DM
+ if (guildId is null) // assume it is a DM
{
channel = discord.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, discord.State);
+ author = ((SocketDMChannel)channel).Recipient;
}
}
+ author ??= discord.State.GetOrAddUser(msg.Value.Author.Value.Id, _ => SocketGlobalUser.Create(discord, discord.State, msg.Value.Author.Value));
+
var message = SocketMessage.Create(discord, discord.State, author, channel, msg.Value);
Messages.Add(message.Id, message);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
index e5776a089..f5abb2c49 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
@@ -127,7 +127,7 @@ namespace Discord.WebSocket
refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id);
}
else
- refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id);
+ refMsgAuthor = (Channel as SocketChannel)?.GetUser(refMsg.Author.Value.Id);
if (refMsgAuthor == null)
refMsgAuthor = SocketUnknownUser.Create(Discord, state, refMsg.Author.Value);
}
diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs
index 1e90b8f5c..b6a61cfb0 100644
--- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs
+++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs
@@ -63,7 +63,7 @@ namespace Discord.WebSocket
=> Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id));
internal SocketRole(SocketGuild guild, ulong id)
- : base(guild.Discord, id)
+ : base(guild?.Discord, id)
{
Guild = guild;
}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
index fdbdeda5e..2ffc75a24 100644
--- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
@@ -12,6 +12,9 @@ namespace Discord
public int Bitrate => throw new NotImplementedException();
public int? UserLimit => throw new NotImplementedException();
+ public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => throw new NotImplementedException();
public ulong? CategoryId => throw new NotImplementedException();