| @@ -1,6 +1,6 @@ | |||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio 15 | # Visual Studio 15 | ||||
| VisualStudioVersion = 15.0.26228.4 | |||||
| VisualStudioVersion = 15.0.26730.12 | |||||
| MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | ||||
| EndProject | EndProject | ||||
| @@ -142,4 +142,10 @@ Global | |||||
| {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | ||||
| {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | ||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(ExtensibilityGlobals) = postSolution | |||||
| SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | |||||
| EndGlobalSection | |||||
| GlobalSection(CodealikeProperties) = postSolution | |||||
| SolutionGuid = a45217b4-a401-4dbf-8654-34d2ec034cd9 | |||||
| EndGlobalSection | |||||
| EndGlobal | EndGlobal | ||||
| @@ -1,24 +1,20 @@ | |||||
| # The Command Service | # The Command Service | ||||
| >[!WARNING] | |||||
| >This article is out of date, and has not been rewritten yet. | |||||
| Information is not guaranteed to be accurate. | |||||
| [Discord.Commands](xref:Discord.Commands) provides an Attribute-based | [Discord.Commands](xref:Discord.Commands) provides an Attribute-based | ||||
| Command Parser. | |||||
| command parser. | |||||
| ## Setup | ## Setup | ||||
| To use Commands, you must create a [Commands Service] and a | |||||
| Command Handler. | |||||
| To use Commands, you must create a [Command Service] and a Command | |||||
| Handler. | |||||
| Included below is a very bare-bones Command Handler. You can extend | |||||
| your Command Handler as much as you like, however the below is the | |||||
| bare minimum. | |||||
| Included below is a very barebone Command Handler. You can extend your | |||||
| Command Handler as much as you like; however, the below is the bare | |||||
| minimum. | |||||
| The CommandService optionally will accept a [CommandServiceConfig], | |||||
| The `CommandService` will optionally accept a [CommandServiceConfig], | |||||
| which _does_ set a few default values for you. It is recommended to | which _does_ set a few default values for you. It is recommended to | ||||
| look over the properties in [CommandServiceConfig], and their default | |||||
| look over the properties in [CommandServiceConfig] and their default | |||||
| values. | values. | ||||
| [!code-csharp[Command Handler](samples/command_handler.cs)] | [!code-csharp[Command Handler](samples/command_handler.cs)] | ||||
| @@ -28,32 +24,32 @@ values. | |||||
| ## With Attributes | ## With Attributes | ||||
| In 1.0, Commands can be defined ahead of time, with attributes, or | |||||
| at runtime, with builders. | |||||
| In 1.0, Commands can be defined ahead of time with attributes, or at | |||||
| runtime with builders. | |||||
| For most bots, ahead-of-time commands should be all you need, and this | |||||
| is the recommended method of defining commands. | |||||
| For most bots, ahead-of-time Commands should be all you need, and this | |||||
| is the recommended method of defining Commands. | |||||
| ### Modules | ### Modules | ||||
| The first step to creating commands is to create a _module_. | |||||
| The first step to creating Commands is to create a _module_. | |||||
| Modules are an organizational pattern that allow you to write your | |||||
| commands in different classes, and have them automatically loaded. | |||||
| A Module is an organizational pattern that allows you to write your | |||||
| Commands in different classes and have them automatically loaded. | |||||
| Discord.Net's implementation of Modules is influenced heavily from | Discord.Net's implementation of Modules is influenced heavily from | ||||
| ASP.Net Core's Controller pattern. This means that the lifetime of a | |||||
| module instance is only as long as the command being invoked. | |||||
| ASP.NET Core's Controller pattern. This means that the lifetime of a | |||||
| module instance is only as long as the Command is being invoked. | |||||
| **Avoid using long-running code** in your modules wherever possible. | **Avoid using long-running code** in your modules wherever possible. | ||||
| You should **not** be implementing very much logic into your modules; | |||||
| outsource to a service for that. | |||||
| You should **not** be implementing very much logic into your modules, | |||||
| instead, outsource to a service for that. | |||||
| If you are unfamiliar with Inversion of Control, it is recommended to | If you are unfamiliar with Inversion of Control, it is recommended to | ||||
| read the MSDN article on [IoC] and [Dependency Injection]. | read the MSDN article on [IoC] and [Dependency Injection]. | ||||
| To begin, create a new class somewhere in your project, and | |||||
| inherit the class from [ModuleBase]. This class **must** be `public`. | |||||
| To begin, create a new class somewhere in your project and inherit the | |||||
| class from [ModuleBase]. This class **must** be `public`. | |||||
| >[!NOTE] | >[!NOTE] | ||||
| >[ModuleBase] is an _abstract_ class, meaning that you may extend it | >[ModuleBase] is an _abstract_ class, meaning that you may extend it | ||||
| @@ -61,6 +57,7 @@ inherit the class from [ModuleBase]. This class **must** be `public`. | |||||
| >extension of ModuleBase. | >extension of ModuleBase. | ||||
| By now, your module should look like this: | By now, your module should look like this: | ||||
| [!code-csharp[Empty Module](samples/empty-module.cs)] | [!code-csharp[Empty Module](samples/empty-module.cs)] | ||||
| [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx | [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx | ||||
| @@ -69,72 +66,75 @@ By now, your module should look like this: | |||||
| ### Adding Commands | ### Adding Commands | ||||
| The next step to creating commands, is actually creating commands. | |||||
| The next step to creating Commands is actually creating the Commands. | |||||
| To create a command, add a method to your module of type `Task`. | |||||
| Typically, you will want to mark this method as `async`, although it is | |||||
| not required. | |||||
| To create a Command, add a method to your module of type `Task`. | |||||
| Typically, you will want to mark this method as `async`, although it | |||||
| is not required. | |||||
| Adding parameters to a command is done by adding parameters to the | |||||
| Adding parameters to a Command is done by adding parameters to the | |||||
| parent Task. | parent Task. | ||||
| For example, to take an integer as an argument, add `int arg`. To take | |||||
| a user as an argument, add `IUser user`. In 1.0, a command can accept | |||||
| nearly any type of argument; a full list of types that are parsed by | |||||
| default can be found in the below section on _Type Readers_. | |||||
| For example, to take an integer as an argument from the user, add `int | |||||
| arg`; to take a user as an argument from the user, add `IUser user`. | |||||
| In 1.0, a Command can accept nearly any type of argument; a full list | |||||
| of types that are parsed by default can be found in the below section | |||||
| on _Type Readers_. | |||||
| Parameters, by default, are always required. To make a parameter | Parameters, by default, are always required. To make a parameter | ||||
| optional, give it a default value. To accept a comma-separated list, | optional, give it a default value. To accept a comma-separated list, | ||||
| set the parameter to `params Type[]`. | set the parameter to `params Type[]`. | ||||
| Should a parameter include spaces, it **must** be wrapped in quotes. | Should a parameter include spaces, it **must** be wrapped in quotes. | ||||
| For example, for a command with a parameter `string food`, you would | |||||
| For example, for a Command with a parameter `string food`, you would | |||||
| execute it with `!favoritefood "Key Lime Pie"`. | execute it with `!favoritefood "Key Lime Pie"`. | ||||
| If you would like a parameter to parse until the end of a command, | |||||
| If you would like a parameter to parse until the end of a Command, | |||||
| flag the parameter with the [RemainderAttribute]. This will allow a | flag the parameter with the [RemainderAttribute]. This will allow a | ||||
| user to invoke a command without wrapping a parameter in quotes. | |||||
| user to invoke a Command without wrapping a parameter in quotes. | |||||
| Finally, flag your command with the [CommandAttribute]. (You must | |||||
| specify a name for this command, except for when it is part of a | |||||
| module group - see below). | |||||
| Finally, flag your Command with the [CommandAttribute] (you must | |||||
| specify a name for this Command, except for when it is part of a | |||||
| Module Group - see below). | |||||
| [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute | [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute | ||||
| [CommandAttribute]: xref:Discord.Commands.CommandAttribute | [CommandAttribute]: xref:Discord.Commands.CommandAttribute | ||||
| ### Command Overloads | ### Command Overloads | ||||
| You may add overloads of your commands, and the command parser will | |||||
| You may add overloads to your Commands, and the Command parser will | |||||
| automatically pick up on it. | automatically pick up on it. | ||||
| If, for whatever reason, you have too commands which are ambiguous to | |||||
| If for whatever reason, you have two Commands which are ambiguous to | |||||
| each other, you may use the @Discord.Commands.PriorityAttribute to | each other, you may use the @Discord.Commands.PriorityAttribute to | ||||
| specify which should be tested before the other. | specify which should be tested before the other. | ||||
| Priority's are sorted in ascending order; the higher priority will be | |||||
| called first. | |||||
| The `Priority` attributes are sorted in ascending order; the higher | |||||
| priority will be called first. | |||||
| ### CommandContext | |||||
| ### Command Context | |||||
| Every command can access the execution context through the [Context] | |||||
| property on [ModuleBase]. CommandContext allows you to access the | |||||
| message, channel, guild, and user that the command was invoked from, | |||||
| as well as the underlying discord client the command was invoked from. | |||||
| Every Command can access the execution context through the [Context] | |||||
| property on [ModuleBase]. `ICommandContext` allows you to access the | |||||
| message, channel, guild, and user that the Command was invoked from, | |||||
| as well as the underlying Discord client that the Command was invoked | |||||
| from. | |||||
| Different types of Contexts may be specified using the generic variant | Different types of Contexts may be specified using the generic variant | ||||
| of [ModuleBase]. When using a [SocketCommandContext], for example, | |||||
| the properties on this context will already be Socket entities. You | |||||
| of [ModuleBase]. When using a [SocketCommandContext], for example, the | |||||
| properties on this context will already be Socket entities, so you | |||||
| will not need to cast them. | will not need to cast them. | ||||
| To reply to messages, you may also invoke [ReplyAsync], instead of | To reply to messages, you may also invoke [ReplyAsync], instead of | ||||
| accessing the channel through the [Context] and sending a message. | accessing the channel through the [Context] and sending a message. | ||||
| > [!WARNING] | |||||
| >Contexts should **NOT** be mixed! You cannot have one module that | |||||
| >uses `CommandContext` and another that uses `SocketCommandContext`. | |||||
| [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context | [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context | ||||
| [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext | [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext | ||||
| >![WARNING] | |||||
| >Contexts should **NOT** be mixed! You cannot have one module that | |||||
| >uses CommandContext, and another that uses SocketCommandContext. | |||||
| [ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_ | |||||
| ### Example Module | ### Example Module | ||||
| @@ -144,50 +144,50 @@ At this point, your module should look comparable to this example: | |||||
| #### Loading Modules Automatically | #### Loading Modules Automatically | ||||
| The Command Service can automatically discover all classes in an | The Command Service can automatically discover all classes in an | ||||
| Assembly that inherit [ModuleBase], and load them. | |||||
| Assembly that inherit [ModuleBase] and load them. | |||||
| To opt a module out of auto-loading, flag it with | To opt a module out of auto-loading, flag it with | ||||
| [DontAutoLoadAttribute] | |||||
| [DontAutoLoadAttribute]. | |||||
| Invoke [CommandService.AddModulesAsync] to discover modules and | Invoke [CommandService.AddModulesAsync] to discover modules and | ||||
| install them. | install them. | ||||
| [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute | [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute | ||||
| [CommandService.AddModulesAsync]: xref:Discord_Commands_CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ | |||||
| [CommandService.AddModulesAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModulesAsync_Assembly_ | |||||
| #### Loading Modules Manually | #### Loading Modules Manually | ||||
| To manually load a module, invoke [CommandService.AddModuleAsync], | |||||
| by passing in the generic type of your module, and optionally | |||||
| a dependency map. | |||||
| To manually load a module, invoke [CommandService.AddModuleAsync] by | |||||
| passing in the generic type of your module and optionally, a | |||||
| dependency map. | |||||
| [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 | [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 | ||||
| ### Module Constructors | ### Module Constructors | ||||
| Modules are constructed using Dependency Injection. Any parameters | Modules are constructed using Dependency Injection. Any parameters | ||||
| that are placed in the constructor must be injected into an | |||||
| @System.IServiceProvider. Alternatively, you may accept an | |||||
| IServiceProvider as an argument and extract services yourself. | |||||
| that are placed in the Module's constructor must be injected into an | |||||
| @System.IServiceProvider first. Alternatively, you may accept an | |||||
| `IServiceProvider` as an argument and extract services yourself. | |||||
| ### Module Properties | ### Module Properties | ||||
| Modules with public settable properties will have them injected after module | |||||
| construction. | |||||
| Modules with `public` settable properties will have the dependencies | |||||
| injected after the construction of the Module. | |||||
| ### Module Groups | ### Module Groups | ||||
| Module Groups allow you to create a module where commands are prefixed. | |||||
| To create a group, flag a module with the | |||||
| @Discord.Commands.GroupAttribute | |||||
| Module Groups allow you to create a module where Commands are | |||||
| prefixed. To create a group, flag a module with the | |||||
| @Discord.Commands.GroupAttribute. | |||||
| Module groups also allow you to create **nameless commands**, where the | |||||
| [CommandAttribute] is configured with no name. In this case, the | |||||
| command will inherit the name of the group it belongs to. | |||||
| Module groups also allow you to create **nameless Commands**, where | |||||
| the [CommandAttribute] is configured with no name. In this case, the | |||||
| Command will inherit the name of the group it belongs to. | |||||
| ### Submodules | ### Submodules | ||||
| Submodules are modules that reside within another module. Typically, | |||||
| Submodules are Modules that reside within another one. Typically, | |||||
| submodules are used to create nested groups (although not required to | submodules are used to create nested groups (although not required to | ||||
| create nested groups). | create nested groups). | ||||
| @@ -199,54 +199,62 @@ create nested groups). | |||||
| ## Dependency Injection | ## Dependency Injection | ||||
| The commands service is bundled with a very barebones Dependency | |||||
| Injection service for your convienence. It is recommended that | |||||
| you use DI when writing your modules. | |||||
| The 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. | |||||
| ### Setup | ### Setup | ||||
| First, you need to create an @System.IServiceProvider | |||||
| You may create your own IServiceProvider if you wish. | |||||
| First, you need to create an @System.IServiceProvider; you may create | |||||
| your own one if you wish. | |||||
| Next, add the dependencies your modules will use to the map. | |||||
| Next, add the dependencies that your modules will use to the map. | |||||
| Finally, pass the map into the `LoadAssembly` method. | |||||
| Your modules will automatically be loaded with this dependency map. | |||||
| Finally, pass the map into the `LoadAssembly` method. Your modules | |||||
| will be automatically loaded with this dependency map. | |||||
| [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] | ||||
| ### Usage in Modules | ### Usage in Modules | ||||
| In the constructor of your module, any parameters will be filled in by | |||||
| the @System.IServiceProvider you pass into `LoadAssembly`. | |||||
| In the constructor of your Module, any parameters will be filled in by | |||||
| the @System.IServiceProvider that you've passed into `LoadAssembly`. | |||||
| Any publicly settable properties will also be filled in the same manner. | |||||
| Any publicly settable properties will also be filled in the same | |||||
| manner. | |||||
| >[!NOTE] | >[!NOTE] | ||||
| > Annotating a property with the [DontInject] attribute will prevent it from | |||||
| being injected. | |||||
| > Annotating a property with a [DontInjectAttribute] attribute will prevent the | |||||
| property from being injected. | |||||
| >[!NOTE] | >[!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 the module was loaded from, and the ServiceProvider passed | |||||
| into it, respectively. | |||||
| >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 `ServiceProvider` that is passed into it respectively. | |||||
| [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | ||||
| [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute | |||||
| # Preconditions | # Preconditions | ||||
| Preconditions serve as a permissions system for your commands. Keep in | |||||
| mind, however, that they are not limited to _just_ permissions, and | |||||
| can be as complex as you want them to be. | |||||
| Precondition serve as a permissions system for your Commands. Keep in | |||||
| mind, however, that they are not limited to _just_ permissions and can | |||||
| be as complex as you want them to be. | |||||
| >[!NOTE] | >[!NOTE] | ||||
| >Preconditions can be applied to Modules, Groups, or Commands. | |||||
| >There are two types of Preconditions. | |||||
| [PreconditionAttribute] can be applied to Modules, Groups, or Commands; | |||||
| [ParameterPreconditionAttribute] can be applied to Parameters. | |||||
| [PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute | |||||
| [ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute | |||||
| ## Bundled Preconditions | ## Bundled Preconditions | ||||
| Commands ships with four bundled preconditions; you may view their | |||||
| usages on their API page. | |||||
| Commands ship with four bundled Preconditions; you may view their | |||||
| usages on their respective API pages. | |||||
| - @Discord.Commands.RequireContextAttribute | - @Discord.Commands.RequireContextAttribute | ||||
| - @Discord.Commands.RequireOwnerAttribute | - @Discord.Commands.RequireOwnerAttribute | ||||
| @@ -255,21 +263,23 @@ usages on their API page. | |||||
| ## Custom Preconditions | ## Custom Preconditions | ||||
| To write your own preconditions, create a new class that inherits from | |||||
| @Discord.Commands.PreconditionAttribute | |||||
| To write your own Precondition, create a new class that inherits from | |||||
| either [PreconditionAttribute] or [ParameterPreconditionAttribute] | |||||
| depending on your use. | |||||
| In order for your precondition to function, you will need to override | |||||
| [CheckPermissions]. | |||||
| In order for your Precondition to function, you will need to override | |||||
| the [CheckPermissions] method. | |||||
| Your IDE should provide an option to fill this in for you. | Your IDE should provide an option to fill this in for you. | ||||
| Return [PreconditionResult.FromSuccess] if the context met the | |||||
| required parameters, otherwise return [PreconditionResult.FromError], | |||||
| optionally including an error message. | |||||
| If the context meets the required parameters, return | |||||
| [PreconditionResult.FromSuccess], otherwise return | |||||
| [PreconditionResult.FromError] and include an error message if | |||||
| necessary. | |||||
| [!code-csharp[Custom Precondition](samples/require_owner.cs)] | [!code-csharp[Custom Precondition](samples/require_owner.cs)] | ||||
| [CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_CommandContext_Discord_Commands_CommandInfo_Discord_Commands_IDependencyMap_ | |||||
| [CheckPermissions]: xref:Discord.Commands.PreconditionAttribute#Discord_Commands_PreconditionAttribute_CheckPermissions_Discord_Commands_ICommandContext_Discord_Commands_CommandInfo_IServiceProvider_ | |||||
| [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess | [PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromSuccess | ||||
| [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ | [PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult#Discord_Commands_PreconditionResult_FromError_System_String_ | ||||
| @@ -296,22 +306,28 @@ By default, the following Types are supported arguments: | |||||
| ### Creating a Type Readers | ### Creating a Type Readers | ||||
| To create a TypeReader, create a new class that imports @Discord and | |||||
| @Discord.Commands. Ensure your class inherits from @Discord.Commands.TypeReader | |||||
| To create a `TypeReader`, create a new class that imports @Discord and | |||||
| @Discord.Commands and ensure the class inherits from | |||||
| @Discord.Commands.TypeReader. | |||||
| Next, satisfy the `TypeReader` class by overriding [Read]. | |||||
| Next, satisfy the `TypeReader` class by overriding the [Read] method. | |||||
| >[!NOTE] | >[!NOTE] | ||||
| >In many cases, Visual Studio can fill this in for you, using the | >In many cases, Visual Studio can fill this in for you, using the | ||||
| >"Implement Abstract Class" IntelliSense hint. | >"Implement Abstract Class" IntelliSense hint. | ||||
| Inside this task, add whatever logic you need to parse the input string. | |||||
| Inside this task, add whatever logic you need to parse the input | |||||
| string. | |||||
| Finally, return a `TypeReaderResult`. If you were able to successfully | |||||
| parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`. | |||||
| Otherwise, return `TypeReaderResult.FromError`. | |||||
| If you are able to successfully parse the input, return | |||||
| [TypeReaderResult.FromSuccess] with the parsed input, otherwise return | |||||
| [TypeReaderResult.FromError] and include an error message if | |||||
| necessary. | |||||
| [Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_ | |||||
| [TypeReaderResult]: xref:Discord.Commands.TypeReaderResult | |||||
| [TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromSuccess_Discord_Commands_TypeReaderValue_ | |||||
| [TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult#Discord_Commands_TypeReaderResult_FromError_Discord_Commands_CommandError_System_String_ | |||||
| [Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_ICommandContext_System_String_IServiceProvider_ | |||||
| #### Sample | #### Sample | ||||
| @@ -319,5 +335,9 @@ Otherwise, return `TypeReaderResult.FromError`. | |||||
| ### Installing TypeReaders | ### Installing TypeReaders | ||||
| TypeReaders are not automatically discovered by the Command Service, | |||||
| and must be explicitly added. To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_). | |||||
| TypeReaders are not automatically discovered by the Command Service | |||||
| and must be explicitly added. | |||||
| To install a TypeReader, invoke [CommandService.AddTypeReader]. | |||||
| [CommandService.AddTypeReader]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_ | |||||
| @@ -8,39 +8,42 @@ using Microsoft.Extensions.DependencyInjection; | |||||
| public class Program | public class Program | ||||
| { | { | ||||
| private CommandService commands; | |||||
| private DiscordSocketClient client; | |||||
| private IServiceProvider services; | |||||
| private CommandService _commands; | |||||
| private DiscordSocketClient _client; | |||||
| private IServiceProvider _services; | |||||
| static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult(); | |||||
| private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); | |||||
| public async Task Start() | |||||
| public async Task StartAsync() | |||||
| { | { | ||||
| client = new DiscordSocketClient(); | |||||
| commands = new CommandService(); | |||||
| _client = new DiscordSocketClient(); | |||||
| _commands = new CommandService(); | |||||
| // Avoid hard coding your token. Use an external source instead in your code. | |||||
| string token = "bot token here"; | string token = "bot token here"; | ||||
| services = new ServiceCollection() | |||||
| .BuildServiceProvider(); | |||||
| _services = new ServiceCollection() | |||||
| .AddSingleton(_client) | |||||
| .AddSingleton(_commands) | |||||
| .BuildServiceProvider(); | |||||
| await InstallCommands(); | |||||
| await InstallCommandsAsync(); | |||||
| await client.LoginAsync(TokenType.Bot, token); | |||||
| await client.StartAsync(); | |||||
| await _client.LoginAsync(TokenType.Bot, token); | |||||
| await _client.StartAsync(); | |||||
| await Task.Delay(-1); | await Task.Delay(-1); | ||||
| } | } | ||||
| public async Task InstallCommands() | |||||
| public async Task InstallCommandsAsync() | |||||
| { | { | ||||
| // Hook the MessageReceived Event into our Command Handler | // Hook the MessageReceived Event into our Command Handler | ||||
| client.MessageReceived += HandleCommand; | |||||
| _client.MessageReceived += HandleCommandAsync; | |||||
| // Discover all of the commands in this assembly and load them. | // Discover all of the commands in this assembly and load them. | ||||
| await commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| } | } | ||||
| public async Task HandleCommand(SocketMessage messageParam) | |||||
| private async Task HandleCommandAsync(SocketMessage messageParam) | |||||
| { | { | ||||
| // Don't process the command if it was a System Message | // Don't process the command if it was a System Message | ||||
| var message = messageParam as SocketUserMessage; | var message = messageParam as SocketUserMessage; | ||||
| @@ -48,13 +51,13 @@ public class Program | |||||
| // Create a number to track where the prefix ends and the command begins | // Create a number to track where the prefix ends and the command begins | ||||
| int argPos = 0; | int argPos = 0; | ||||
| // Determine if the message is a command, based on if it starts with '!' or a mention prefix | // Determine if the message is a command, based on if it starts with '!' or a mention prefix | ||||
| if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return; | |||||
| if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; | |||||
| // Create a Command Context | // Create a Command Context | ||||
| var context = new CommandContext(client, message); | |||||
| var context = new SocketCommandContext(_client, message); | |||||
| // Execute the command. (result does not indicate a return value, | // Execute the command. (result does not indicate a return value, | ||||
| // rather an object stating if the command executed successfully) | // rather an object stating if the command executed successfully) | ||||
| var result = await commands.ExecuteAsync(context, argPos, service); | |||||
| var result = await _commands.ExecuteAsync(context, argPos, _services); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| await context.Channel.SendMessageAsync(result.ErrorReason); | await context.Channel.SendMessageAsync(result.ErrorReason); | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -1,18 +1,18 @@ | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| using foxboat.Services; | |||||
| private IServiceProvider _services; | |||||
| private CommandService _commands; | |||||
| public class Commands | |||||
| public async Task InstallAsync(DiscordSocketClient client) | |||||
| { | { | ||||
| public async Task Install(DiscordSocketClient client) | |||||
| { | |||||
| // Here, we will inject the ServiceProvider with | |||||
| // all of the services our client will use. | |||||
| _serviceCollection.AddSingleton(client) | |||||
| _serviceCollection.AddSingleton(new NotificationService()) | |||||
| _serviceCollection.AddSingleton(new DatabaseService()) | |||||
| // ... | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| } | |||||
| } | |||||
| // Here, we will inject the ServiceProvider with | |||||
| // all of the services our client will use. | |||||
| _services = 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. | |||||
| .AddSingleton<DatabaseService>() | |||||
| .BuildServiceProvider(); | |||||
| // ... | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| using Discord.Commands; | using Discord.Commands; | ||||
| public class InfoModule : ModuleBase | |||||
| public class InfoModule : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| } | } | ||||
| @@ -1,8 +1,8 @@ | |||||
| [Group("admin")] | [Group("admin")] | ||||
| public class AdminModule : ModuleBase | |||||
| public class AdminModule : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| [Group("clean")] | [Group("clean")] | ||||
| public class CleanModule : ModuleBase | |||||
| public class CleanModule : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| // ~admin clean 15 | // ~admin clean 15 | ||||
| [Command] | [Command] | ||||
| @@ -1,42 +1,41 @@ | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| // Create a module with no prefix | // Create a module with no prefix | ||||
| public class Info : ModuleBase | |||||
| public class Info : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| // ~say hello -> hello | |||||
| [Command("say"), Summary("Echos a message.")] | |||||
| public async Task Say([Remainder, Summary("The text to echo")] string echo) | |||||
| { | |||||
| // ReplyAsync is a method on ModuleBase | |||||
| await ReplyAsync(echo); | |||||
| } | |||||
| // ~say hello -> hello | |||||
| [Command("say")] | |||||
| [Summary("Echos a message.")] | |||||
| public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo) | |||||
| { | |||||
| // ReplyAsync is a method on ModuleBase | |||||
| await ReplyAsync(echo); | |||||
| } | |||||
| } | } | ||||
| // Create a module with the 'sample' prefix | // Create a module with the 'sample' prefix | ||||
| [Group("sample")] | [Group("sample")] | ||||
| public class Sample : ModuleBase | |||||
| public class Sample : ModuleBase<SocketCommandContext> | |||||
| { | { | ||||
| // ~sample square 20 -> 400 | |||||
| [Command("square"), Summary("Squares a number.")] | |||||
| public async Task Square([Summary("The number to square.")] int num) | |||||
| { | |||||
| // We can also access the channel from the Command Context. | |||||
| await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); | |||||
| } | |||||
| // ~sample square 20 -> 400 | |||||
| [Command("square")] | |||||
| [Summary("Squares a number.")] | |||||
| public async Task SquareAsync([Summary("The number to square.")] int num) | |||||
| { | |||||
| // We can also access the channel from the Command Context. | |||||
| await Context.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); | |||||
| } | |||||
| // ~sample userinfo --> foxbot#0282 | |||||
| // ~sample userinfo --> foxbot#0282 | |||||
| // ~sample userinfo @Khionu --> Khionu#8708 | // ~sample userinfo @Khionu --> Khionu#8708 | ||||
| // ~sample userinfo Khionu#8708 --> Khionu#8708 | // ~sample userinfo Khionu#8708 --> Khionu#8708 | ||||
| // ~sample userinfo Khionu --> Khionu#8708 | // ~sample userinfo Khionu --> Khionu#8708 | ||||
| // ~sample userinfo 96642168176807936 --> Khionu#8708 | // ~sample userinfo 96642168176807936 --> Khionu#8708 | ||||
| // ~sample whois 96642168176807936 --> Khionu#8708 | |||||
| [Command("userinfo"), Summary("Returns info about the current user, or the user parameter, if one passed.")] | |||||
| [Alias("user", "whois")] | |||||
| public async Task UserInfo([Summary("The (optional) user to get info for")] IUser user = null) | |||||
| { | |||||
| var userInfo = user ?? Context.Client.CurrentUser; | |||||
| await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); | |||||
| } | |||||
| } | |||||
| // ~sample whois 96642168176807936 --> Khionu#8708 | |||||
| [Command("userinfo")] | |||||
| [Summary("Returns info about the current user, or the user parameter, if one passed.")] | |||||
| [Alias("user", "whois")] | |||||
| public async Task UserInfoAsync([Summary("The (optional) user to get info for")] SocketUser user = null) | |||||
| { | |||||
| var userInfo = user ?? Context.Client.CurrentUser; | |||||
| await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); | |||||
| } | |||||
| } | |||||
| @@ -12,7 +12,7 @@ Discord API. | |||||
| ### Inheritance | ### Inheritance | ||||
| Due to the nature of the Discord API, some entities are designed with | Due to the nature of the Discord API, some entities are designed with | ||||
| multiple variants, for example, `SocketUser` and `SocketGuildUser`. | |||||
| multiple variants; for example, `SocketUser` and `SocketGuildUser`. | |||||
| All models will contain the most detailed version of an entity | All models will contain the most detailed version of an entity | ||||
| possible, even if the type is less detailed. | possible, even if the type is less detailed. | ||||
| @@ -61,8 +61,11 @@ a variant of the type that you need. | |||||
| ### Tips | ### Tips | ||||
| Avoid using boxing-casts to coerce entities into a variant, use the | Avoid using boxing-casts to coerce entities into a variant, use the | ||||
| `as` keyword, and a null-conditional operator. | |||||
| [`as`] keyword, and a null-conditional operator instead. | |||||
| This allows you to write safer code, and avoid InvalidCastExceptions. | |||||
| This allows you to write safer code and avoid [InvalidCastExceptions]. | |||||
| For example, `(message.Author as SocketGuildUser)?.Nickname`. | |||||
| For example, `(message.Author as SocketGuildUser)?.Nickname`. | |||||
| [`as`]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as | |||||
| [InvalidCastExceptions]: https://msdn.microsoft.com/en-us/library/system.invalidcastexception(v=vs.110).aspx | |||||
| @@ -4,27 +4,27 @@ title: Working with Events | |||||
| Events in Discord.Net are consumed in a similar manner to the standard | Events in Discord.Net are consumed in a similar manner to the standard | ||||
| convention, with the exception that every event must be of the type | convention, with the exception that every event must be of the type | ||||
| `System.Threading.Tasks.Task`, and instead of using EventArgs, the | |||||
| `System.Threading.Tasks.Task` and instead of using `EventArgs`, the | |||||
| event's parameters are passed directly into the handler. | event's parameters are passed directly into the handler. | ||||
| This allows for events to be handled in an async context directly, | |||||
| instead of relying on async void. | |||||
| This allows for events to be handled in an async context directly | |||||
| instead of relying on `async void`. | |||||
| ### Usage | ### Usage | ||||
| To receive data from an event, hook into it using C#'s delegate | To receive data from an event, hook into it using C#'s delegate | ||||
| event pattern. | event pattern. | ||||
| You may opt either to hook an event to an anonymous function (lambda) | |||||
| You may either opt to hook an event to an anonymous function (lambda) | |||||
| or a named function. | or a named function. | ||||
| ### Safety | ### Safety | ||||
| All events are designed to be thread-safe, in that events are executed | |||||
| synchronously off the gateway task, in the same context as the gateway | |||||
| All events are designed to be thread-safe; events are executed | |||||
| synchronously off the gateway task in the same context as the gateway | |||||
| task. | task. | ||||
| As a side effect, this makes it possible to deadlock the gateway task, | |||||
| As a side effect, this makes it possible to deadlock the gateway task | |||||
| and kill a connection. As a general rule of thumb, any task that takes | and kill a connection. As a general rule of thumb, any task that takes | ||||
| longer than three seconds should **not** be awaited directly in the | longer than three seconds should **not** be awaited directly in the | ||||
| context of an event, but should be wrapped in a `Task.Run` or | context of an event, but should be wrapped in a `Task.Run` or | ||||
| @@ -62,7 +62,7 @@ This pattern is typically only found on `EntityUpdated` events. | |||||
| An event handler with a signature of `Func<Cacheable, Entity, Task>` | An event handler with a signature of `Func<Cacheable, Entity, Task>` | ||||
| means that the `before` state of the entity was not provided by the | means that the `before` state of the entity was not provided by the | ||||
| API, so it can either be pulled from the client's cache, or | |||||
| API, so it can either be pulled from the client's cache or | |||||
| downloaded from the API. | downloaded from the API. | ||||
| See the documentation for [Cacheable] for more information on this | See the documentation for [Cacheable] for more information on this | ||||
| @@ -76,8 +76,8 @@ object. | |||||
| ### Tips | ### Tips | ||||
| Many events relating to a Message entity, e.g. `MessageUpdated` | |||||
| and `ReactionAdded` rely on the client's message cache, which is | |||||
| Many events relating to a Message entity (i.e. `MessageUpdated` and | |||||
| `ReactionAdded`) rely on the client's message cache, which is | |||||
| **not** enabled by default. Set the `MessageCacheSize` flag in | **not** enabled by default. Set the `MessageCacheSize` flag in | ||||
| [DiscordSocketConfig] to enable it. | [DiscordSocketConfig] to enable it. | ||||
| @@ -14,12 +14,14 @@ section. | |||||
| ### Usage | ### Usage | ||||
| To receive log events, simply hook the discord client's log method | To receive log events, simply hook the discord client's log method | ||||
| to a Task with a single parameter of type [LogMessage] | |||||
| to a `Task` with a single parameter of type [LogMessage]. | |||||
| It is recommended that you use an established function instead of a | It is recommended that you use an established function instead of a | ||||
| lambda for handling logs, because most [addons] accept a reference | |||||
| lambda for handling logs, because most addons accept a reference | |||||
| to a logging function to write their own messages. | to a logging function to write their own messages. | ||||
| [LogMessage]: xref:Discord.LogMessage | |||||
| ### Usage in Commands | ### Usage in Commands | ||||
| Discord.Net's [CommandService] also provides a log event, identical | Discord.Net's [CommandService] also provides a log event, identical | ||||
| @@ -29,6 +31,9 @@ Data logged through this event is typically coupled with a | |||||
| [CommandException], where information about the command's context | [CommandException], where information about the command's context | ||||
| and error can be found and handled. | and error can be found and handled. | ||||
| [CommandService]: xref:Discord.Commands.CommandService | |||||
| [CommandException]: xref:Discord.Commands.CommandException | |||||
| #### Samples | #### Samples | ||||
| [!code-csharp[Logging Sample](samples/logging.cs)] | [!code-csharp[Logging Sample](samples/logging.cs)] | ||||
| @@ -2,84 +2,87 @@ | |||||
| title: Installing Discord.Net | title: Installing Discord.Net | ||||
| --- | --- | ||||
| Discord.Net is distributed through the NuGet package manager, and it is | |||||
| recommended to use NuGet to get started. | |||||
| Discord.Net is distributed through the NuGet package manager, and it | |||||
| is recommended to use NuGet to get started. | |||||
| Optionally, you may compile from source and install yourself. | Optionally, you may compile from source and install yourself. | ||||
| # Supported Platforms | # Supported Platforms | ||||
| Currently, Discord.Net targets [.NET Standard] 1.3, and offers support for | |||||
| .NET Standard 1.1. If your application will be targeting .NET Standard 1.1, | |||||
| please see the [additional steps](#installing-on-net-standard-11). | |||||
| Currently, Discord.Net targets [.NET Standard] 1.3 and offers support | |||||
| for .NET Standard 1.1. If your application will be targeting .NET | |||||
| Standard 1.1, please see the [additional steps]. | |||||
| Since Discord.Net is built on the .NET Standard, it is also recommended to | |||||
| create applications using [.NET Core], though you are not required to. When | |||||
| using .NET Framework, it is suggested to target `.NET 4.6.1` or higher. | |||||
| Since Discord.Net is built on the .NET Standard, it is also | |||||
| recommended to create applications using [.NET Core], though not | |||||
| required. When using .NET Framework, it is suggested to target | |||||
| `.NET Framework 4.6.1` or higher. | |||||
| [.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library | [.NET Standard]: https://docs.microsoft.com/en-us/dotnet/articles/standard/library | ||||
| [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ | [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ | ||||
| [additional steps]: #installing-on-net-standard-11 | |||||
| # Installing with NuGet | # Installing with NuGet | ||||
| Release builds of Discord.Net 1.0 will be published to the | Release builds of Discord.Net 1.0 will be published to the | ||||
| [official NuGet feed]. | [official NuGet feed]. | ||||
| Development builds of Discord.Net 1.0, as well as [addons](TODO) are published | |||||
| to our development [MyGet feed]. | |||||
| Development builds of Discord.Net 1.0, as well as addons *(TODO)* are | |||||
| published to our development [MyGet feed]. | |||||
| Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` | Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` | ||||
| Not sure how to add a direct feed? See how [with Visual Studio] | |||||
| or [without Visual Studio](#configuring-nuget-without-visual-studio) | |||||
| Not sure how to add a direct feed? See how [with Visual Studio] or | |||||
| [without Visual Studio]. | |||||
| [official NuGet feed]: https://nuget.org | [official NuGet feed]: https://nuget.org | ||||
| [MyGet feed]: https://www.myget.org/feed/Packages/discord-net | [MyGet feed]: https://www.myget.org/feed/Packages/discord-net | ||||
| [with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources | [with Visual Studio]: https://docs.microsoft.com/en-us/nuget/tools/package-manager-ui#package-sources | ||||
| [without Visual Studio]: #configuring-nuget-without-visual-studio | |||||
| ## Using Visual Studio | ## Using Visual Studio | ||||
| 1. Create a solution for your bot | |||||
| 2. In Solution Explorer, find the 'Dependencies' element under your bot's | |||||
| project | |||||
| 3. Right click on 'Dependencies', and select 'Manage NuGet packages' | |||||
|  | |||||
| 4. In the 'browse' tab, search for 'Discord.Net' | |||||
| > [!TIP] | > [!TIP] | ||||
| Don't forget to change your package source if you're installing from the | |||||
| developer feed. | |||||
| Also make sure to check 'Enable Prereleases' if installing a dev build! | |||||
| 5. Install the 'Discord.Net' package | |||||
| >Don't forget to change your package source if you're installing from | |||||
| the developer feed. | |||||
| >Also make sure to check "Enable Prereleases" if installing a dev | |||||
| build! | |||||
| 1. Create a solution for your bot. | |||||
| 2. In Solution Explorer, find the "Dependencies" element under your | |||||
| bot's project. | |||||
| 3. Right click on "Dependencies", and select "Manage NuGet packages." | |||||
|  | |||||
| 4. In the "Browse" tab, search for `Discord.Net`. | |||||
| 5. Install the `Discord.Net` package. | |||||
|  |  | ||||
| ## Using JetBrains Rider | ## Using JetBrains Rider | ||||
| 1. Create a new solution for your bot | |||||
| 2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution) | |||||
|  | |||||
| 3. In the 'Packages' tab, search for 'Discord.Net' | |||||
|  | |||||
| > [!TIP] | > [!TIP] | ||||
| Make sure to check the 'Prerelease' box if installing a dev build! | |||||
| Make sure to check the "Prerelease" box if installing a dev build! | |||||
| 4. Install by adding the package to your project | |||||
| 1. Create a new solution for your bot. | |||||
| 2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for | |||||
| Solution). | |||||
|  | |||||
| 3. In the "Packages" tab, search for `Discord.Net`. | |||||
|  | |||||
| 4. Install by adding the package to your project. | |||||
|  |  | ||||
| ## Using Visual Studio Code | ## Using Visual Studio Code | ||||
| 1. Create a new project for your bot | |||||
| 2. Add Discord.Net to your .csproj | |||||
| > [!TIP] | |||||
| Don't forget to add the package source to a [NuGet.Config file] if | |||||
| you're installing from the developer feed. | |||||
| 1. Create a new project for your bot. | |||||
| 2. Add `Discord.Net` to your .csproj. | |||||
| [!code-xml[Sample .csproj](samples/project.csproj)] | [!code-xml[Sample .csproj](samples/project.csproj)] | ||||
| > [!TIP] | |||||
| Don't forget to add the package source to a [NuGet.Config file](#configuring-nuget-without-visual-studio) if you're installing from the | |||||
| developer feed. | |||||
| [NuGet.Config file]: #configuring-nuget-without-visual-studio | |||||
| # Compiling from Source | # Compiling from Source | ||||
| @@ -90,8 +93,8 @@ In order to compile Discord.Net, you require the following: | |||||
| - [Visual Studio 2017](https://www.visualstudio.com/) | - [Visual Studio 2017](https://www.visualstudio.com/) | ||||
| - [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) | - [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) | ||||
| The .NET Core and Docker (Preview) workload is required during Visual Studio | |||||
| installation. | |||||
| The .NET Core and Docker (Preview) workload is required during Visual | |||||
| Studio installation. | |||||
| ### Using Command Line | ### Using Command Line | ||||
| @@ -101,26 +104,27 @@ installation. | |||||
| ## Installing on .NET Standard 1.1 | ## Installing on .NET Standard 1.1 | ||||
| For applications targeting a runtime corresponding with .NET Standard 1.1 or 1.2, | |||||
| the builtin WebSocket and UDP provider will not work. For applications which | |||||
| utilize a WebSocket connection to Discord (WebSocket or RPC), third-party | |||||
| provider packages will need to be installed and configured. | |||||
| For applications targeting a runtime corresponding with .NET Standard | |||||
| 1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For | |||||
| applications which utilize a WebSocket connection to Discord | |||||
| (WebSocket or RPC), third-party provider packages will need to be | |||||
| installed and configured. | |||||
| First, install the following packages through NuGet, or compile yourself, if | |||||
| you prefer: | |||||
| First, install the following packages through NuGet, or compile | |||||
| yourself, if you prefer: | |||||
| - Discord.Net.Providers.WS4Net | - Discord.Net.Providers.WS4Net | ||||
| - Discord.Net.Providers.UDPClient | - Discord.Net.Providers.UDPClient | ||||
| Note that `Discord.Net.Providers.UDPClient` is _only_ required if your bot will | |||||
| be utilizing voice chat. | |||||
| Note that `Discord.Net.Providers.UDPClient` is _only_ required if your | |||||
| bot will be utilizing voice chat. | |||||
| Next, you will need to configure your [DiscordSocketClient] to use these custom | |||||
| providers over the default ones. | |||||
| Next, you will need to configure your [DiscordSocketClient] to use | |||||
| these custom providers over the default ones. | |||||
| To do this, set the `WebSocketProvider` and optionally `UdpSocketProvider` | |||||
| properties on the [DiscordSocketConfig] that you are passing into your | |||||
| client. | |||||
| To do this, set the `WebSocketProvider` and the optional | |||||
| `UdpSocketProvider` properties on the [DiscordSocketConfig] that you | |||||
| are passing into your client. | |||||
| [!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] | [!code-csharp[NET Standard 1.1 Example](samples/netstd11.cs)] | ||||
| @@ -129,13 +133,14 @@ client. | |||||
| ## Configuring NuGet without Visual Studio | ## Configuring NuGet without Visual Studio | ||||
| If you plan on deploying your bot or developing outside of Visual Studio, you | |||||
| will need to create a local NuGet configuration file for your project. | |||||
| If you plan on deploying your bot or developing outside of Visual | |||||
| Studio, you will need to create a local NuGet configuration file for | |||||
| your project. | |||||
| To do this, create a file named `nuget.config` alongside the root of your | |||||
| application, where the project solution is located. | |||||
| To do this, create a file named `nuget.config` alongside the root of | |||||
| your application, where the project solution is located. | |||||
| Paste the following snippets into this configuration file, adding any additional | |||||
| feeds as necessary. | |||||
| Paste the following snippets into this configuration file, adding any | |||||
| additional feeds as necessary. | |||||
| [!code-xml[NuGet Configuration](samples/nuget.config)] | [!code-xml[NuGet Configuration](samples/nuget.config)] | ||||
| @@ -13,42 +13,46 @@ diverse commands later, but for now, it is a good starting point. | |||||
| Before you can begin writing your bot, it is necessary to create a bot | Before you can begin writing your bot, it is necessary to create a bot | ||||
| account on Discord. | account on Discord. | ||||
| 1. Visit the [Discord Applications Portal] | |||||
| 2. Create a New Application | |||||
| 1. Visit the [Discord Applications Portal]. | |||||
| 2. Create a New Application. | |||||
| 3. Give the application a name (this will be the bot's initial | 3. Give the application a name (this will be the bot's initial | ||||
| username). | username). | ||||
| 4. Create the Application | |||||
|  | |||||
| 5. In the application review page, click **Create a Bot User** | |||||
|  | |||||
| 6. Confirm the popup | |||||
| 7. If this bot will be public, check 'Public Bot'. | |||||
| **Do not tick any other options!** | |||||
| 4. Create the Application. | |||||
|  | |||||
| 5. In the application review page, click **Create a Bot User**. | |||||
|  | |||||
| 6. Confirm the popup. | |||||
| 7. If this bot will be public, check "Public Bot." **Do not tick any | |||||
| other options!** | |||||
| [Discord Applications Portal]: https://discordapp.com/developers/applications/me | [Discord Applications Portal]: https://discordapp.com/developers/applications/me | ||||
| ## Adding your bot to a server | ## Adding your bot to a server | ||||
| Bots **can not** use invite links, they must be explicitly invited | |||||
| Bots **cannot** use invite links, they must be explicitly invited | |||||
| through the OAuth2 flow. | through the OAuth2 flow. | ||||
| 1. Open your bot's application on the [Discord Applications Portal] | |||||
| 1. Open your bot's application on the [Discord Applications Portal]. | |||||
| 2. Retrieve the app's **Client ID**. | 2. Retrieve the app's **Client ID**. | ||||
|  | |||||
|  | |||||
| 3. Create an OAuth2 authorization URL | 3. Create an OAuth2 authorization URL | ||||
| `https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot` | `https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot` | ||||
| 4. Open the authorization URL in your browser | |||||
| 5. Select a server | |||||
| >[!NOTE] | |||||
| Only servers where you have the `MANAGE_SERVER` permission will be | |||||
| present in this list. | |||||
| 4. Open the authorization URL in your browser. | |||||
| 5. Select a server. | |||||
| 6. Click on authorize. | |||||
| >[!NOTE] | |||||
| Only servers where you have the `MANAGE_SERVER` permission will be | |||||
| present in this list. | |||||
|  | |||||
| 6. Click authorize | |||||
|  | |||||
| ## Connecting to Discord | ## Connecting to Discord | ||||
| @@ -57,10 +61,10 @@ do that now. (see the [Installing](installing.md) section) | |||||
| ### Async | ### Async | ||||
| Discord.Net uses .NET's Task-based Asynchronous Pattern ([TAP]) | |||||
| Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] | |||||
| extensively - nearly every operation is asynchronous. | extensively - nearly every operation is asynchronous. | ||||
| It is highly recommended that these operations be awaited in a | |||||
| It is highly recommended that these operations are awaited in a | |||||
| properly established async context whenever possible. Establishing an | properly established async context whenever possible. Establishing an | ||||
| async context can be problematic, but not hard. | async context can be problematic, but not hard. | ||||
| @@ -70,27 +74,29 @@ async main. | |||||
| [!code-csharp[Async Context](samples/intro/async-context.cs)] | [!code-csharp[Async Context](samples/intro/async-context.cs)] | ||||
| As a result of this, your program will now start, and immidiately | |||||
| jump into an async context. This will allow us later on to create a | |||||
| connection to Discord, without needing to worry about setting up the | |||||
| As a result of this, your program will now start and immidiately | |||||
| jump into an async context. This will allow us to create a connection | |||||
| to Discord later on without needing to worry about setting up the | |||||
| correct async implementation. | correct async implementation. | ||||
| >[!TIP] | >[!TIP] | ||||
| If your application throws any exceptions within an async context, | If your application throws any exceptions within an async context, | ||||
| they will be thrown all the way back up to the first non-async method. | |||||
| Since our first non-async method is the program's Main method, this | |||||
| they will be thrown all the way back up to the first non-async method; | |||||
| since our first non-async method is the program's `Main` method, this | |||||
| means that **all** unhandled exceptions will be thrown up there, which | means that **all** unhandled exceptions will be thrown up there, which | ||||
| will crash your application. Discord.Net will prevent exceptions in | will crash your application. Discord.Net will prevent exceptions in | ||||
| event handlers from crashing your program, but any exceptions in your | event handlers from crashing your program, but any exceptions in your | ||||
| async main **will** cause the application to crash. | async main **will** cause the application to crash. | ||||
| [Task-based Asynchronous Pattern (TAP)]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async | |||||
| ### Creating a logging method | ### Creating a logging method | ||||
| Before we create and configure a Discord client, we will add a method | Before we create and configure a Discord client, we will add a method | ||||
| to handle Discord.Net's log events. | to handle Discord.Net's log events. | ||||
| To allow agnostic support of as many log providers as possible, we | To allow agnostic support of as many log providers as possible, we | ||||
| log information through a Log event, with a proprietary LogMessage | |||||
| log information through a `Log` event with a proprietary `LogMessage` | |||||
| parameter. See the [API Documentation] for this event. | parameter. See the [API Documentation] for this event. | ||||
| If you are using your own logging framework, this is where you would | If you are using your own logging framework, this is where you would | ||||
| @@ -99,10 +105,12 @@ the Console. | |||||
| [!code-csharp[Async Context](samples/intro/logging.cs)] | [!code-csharp[Async Context](samples/intro/logging.cs)] | ||||
| [API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||||
| ### Creating a Discord Client | ### Creating a Discord Client | ||||
| Finally, we can create a connection to Discord. Since we are writing | Finally, we can create a connection to Discord. Since we are writing | ||||
| a bot, we will be using a [DiscordSocketClient], along with socket | |||||
| a bot, we will be using a [DiscordSocketClient] along with socket | |||||
| entities. See the [terminology](terminology.md) if you're unsure of | entities. See the [terminology](terminology.md) if you're unsure of | ||||
| the differences. | the differences. | ||||
| @@ -110,22 +118,24 @@ To do so, create an instance of [DiscordSocketClient] in your async | |||||
| main, passing in a configuration object only if necessary. For most | main, passing in a configuration object only if necessary. For most | ||||
| users, the default will work fine. | users, the default will work fine. | ||||
| Before connecting, we should hook the client's log event to the | |||||
| Before connecting, we should hook the client's `Log` event to the | |||||
| log handler that was just created. Events in Discord.Net work | log handler that was just created. Events in Discord.Net work | ||||
| similarly to other events in C#, so hook this event the way that | similarly to other events in C#, so hook this event the way that | ||||
| you typically would. | you typically would. | ||||
| Next, you will need to 'login to Discord' with the `LoginAsync` method. | |||||
| Next, you will need to "login to Discord" with the `LoginAsync` | |||||
| method. | |||||
| You may create a variable to hold your bot's token (this can be found | You may create a variable to hold your bot's token (this can be found | ||||
| on your bot's application page on the [Discord Applications Portal]). | on your bot's application page on the [Discord Applications Portal]). | ||||
|  |  | ||||
| >[!IMPORTANT] | >[!IMPORTANT] | ||||
| Your bot's token can be used to gain total access to your bot, so | Your bot's token can be used to gain total access to your bot, so | ||||
| **do __NOT__ share this token with anyone!** It may behoove you to | |||||
| store this token in an external file if you plan on distributing the | |||||
| source code for your bot. | |||||
| **do __NOT__ share this token with anyone else!** It may behoove you | |||||
| to store this token in an external file if you plan on distributing | |||||
| the source code for your bot. | |||||
| We may now invoke the client's `StartAsync` method, which will | We may now invoke the client's `StartAsync` method, which will | ||||
| start connection/reconnection logic. It is important to note that | start connection/reconnection logic. It is important to note that | ||||
| @@ -134,14 +144,9 @@ start connection/reconnection logic. It is important to note that | |||||
| Any methods that rely on the client's state should go in an event | Any methods that rely on the client's state should go in an event | ||||
| handler. | handler. | ||||
| >[!NOTE] | |||||
| Connection logic is incomplete as of the current build. Events will | |||||
| soon be added to indicate when the client's state is ready for use; | |||||
| (rewrite this section when possible) | |||||
| Finally, we will want to block the async main method from returning | Finally, we will want to block the async main method from returning | ||||
| until after the application is exited. To do this, we can await an | until after the application is exited. To do this, we can await an | ||||
| infinite delay, or any other blocking method, such as reading from | |||||
| infinite delay or any other blocking method, such as reading from | |||||
| the console. | the console. | ||||
| The following lines can now be added: | The following lines can now be added: | ||||
| @@ -154,51 +159,55 @@ online in Discord. | |||||
| >[!TIP] | >[!TIP] | ||||
| Encountering a `PlatformNotSupportedException` when starting your bot? | Encountering a `PlatformNotSupportedException` when starting your bot? | ||||
| This means that you are targeting a platform where .NET's default | This means that you are targeting a platform where .NET's default | ||||
| WebSocket client is not supported. Refer to the [installing guide] | |||||
| WebSocket client is not supported. Refer to the [installation guide] | |||||
| for how to fix this. | for how to fix this. | ||||
| [TAP]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async | |||||
| [API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||||
| [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | ||||
| [installing guide]: installing.md#installing-on-net-standard-11 | |||||
| [installation guide]: installing.md#installing-on-net-standard-11 | |||||
| ### Handling a 'ping' | ### Handling a 'ping' | ||||
| >[!WARNING] | |||||
| Please note that this is *not* a proper way to create a command. | |||||
| Use the `CommandService` provided by the library instead, as explained | |||||
| in the [Command Guide] section. | |||||
| Now that we have learned how to open a connection to Discord, we can | Now that we have learned how to open a connection to Discord, we can | ||||
| begin handling messages that users are sending. | begin handling messages that users are sending. | ||||
| To start out, our bot will listen for any message where the content | To start out, our bot will listen for any message where the content | ||||
| is equal to `!ping`, and respond back with `Pong!`. | |||||
| is equal to `!ping` and respond back with "Pong!". | |||||
| Since we want to listen for new messages, the event to hook in to | |||||
| Since we want to listen for new messages, the event to hook into | |||||
| is [MessageReceived]. | is [MessageReceived]. | ||||
| In your program, add a method that matches the signature of the | In your program, add a method that matches the signature of the | ||||
| MessageReceived event - it must be a method (`Func`) that returns the | |||||
| type `Task`, and takes a single parameter, a [SocketMessage]. Also, | |||||
| `MessageReceived` event - it must be a method (`Func`) that returns | |||||
| the type `Task` and takes a single parameter, a [SocketMessage]. Also, | |||||
| since we will be sending data to Discord in this method, we will flag | since we will be sending data to Discord in this method, we will flag | ||||
| it as `async`. | it as `async`. | ||||
| In this method, we will add an `if` block, to determine if the message | |||||
| In this method, we will add an `if` block to determine if the message | |||||
| content fits the rules of our scenario - recall that it must be equal | content fits the rules of our scenario - recall that it must be equal | ||||
| to `!ping`. | to `!ping`. | ||||
| Inside the branch of this condition, we will want to send a message | Inside the branch of this condition, we will want to send a message | ||||
| back to the channel from which the message came - `Pong!`. To find the | |||||
| channel, look for the `Channel` property on the message parameter. | |||||
| back to the channel from which the message comes from - "Pong!". To | |||||
| find the channel, look for the `Channel` property on the message | |||||
| parameter. | |||||
| Next, we will want to send a message to this channel. Since the | Next, we will want to send a message to this channel. Since the | ||||
| channel object is of type [SocketMessageChannel], we can invoke the | channel object is of type [SocketMessageChannel], we can invoke the | ||||
| `SendMessageAsync` instance method. For the message content, send back | `SendMessageAsync` instance method. For the message content, send back | ||||
| a string containing 'Pong!'. | |||||
| a string containing "Pong!". | |||||
| You should have now added the following lines: | You should have now added the following lines: | ||||
| [!code-csharp[Message](samples/intro/message.cs)] | [!code-csharp[Message](samples/intro/message.cs)] | ||||
| Now, your first bot is complete. You may continue to add on to this | |||||
| if you desire, but for any bot that will be carrying out multiple | |||||
| commands, it is strongly encouraged to use the command framework, as | |||||
| Now your first bot is complete. You may continue to add on to this | |||||
| if you desire, but for any bots that will be carrying out multiple | |||||
| commands, it is strongly recommended to use the command framework as | |||||
| shown below. | shown below. | ||||
| For your reference, you may view the [completed program]. | For your reference, you may view the [completed program]. | ||||
| @@ -207,13 +216,14 @@ For your reference, you may view the [completed program]. | |||||
| [SocketMessage]: xref:Discord.WebSocket.SocketMessage | [SocketMessage]: xref:Discord.WebSocket.SocketMessage | ||||
| [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel | [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel | ||||
| [completed program]: samples/intro/complete.cs | [completed program]: samples/intro/complete.cs | ||||
| [Command Guide]: ../commands/commands.md | |||||
| # Building a bot with commands | # Building a bot with commands | ||||
| This section will show you how to write a program that is ready for | This section will show you how to write a program that is ready for | ||||
| [commands](commands/commands.md). Note that this will not be explaining _how_ | |||||
| to write commands or services, it will only be covering the general | |||||
| structure. | |||||
| [Commands](../commands/commands.md). Note that we will not be | |||||
| explaining _how_ to write Commands or Services, it will only be | |||||
| covering the general structure. | |||||
| For reference, view an [annotated example] of this structure. | For reference, view an [annotated example] of this structure. | ||||
| @@ -224,4 +234,4 @@ should be to separate the program (initialization and command handler), | |||||
| the modules (handle commands), and the services (persistent storage, | the modules (handle commands), and the services (persistent storage, | ||||
| pure functions, data manipulation). | pure functions, data manipulation). | ||||
| **todo:** diagram of bot structure | |||||
| **todo:** diagram of bot structure | |||||
| @@ -1,7 +1,7 @@ | |||||
| public async Task MainAsync() | public async Task MainAsync() | ||||
| { | { | ||||
| // client.Log ... | // client.Log ... | ||||
| client.MessageReceived += MessageReceived; | |||||
| _client.MessageReceived += MessageReceived; | |||||
| // ... | // ... | ||||
| } | } | ||||
| @@ -11,4 +11,4 @@ private async Task MessageReceived(SocketMessage message) | |||||
| { | { | ||||
| await message.Channel.SendMessageAsync("Pong!"); | await message.Channel.SendMessageAsync("Pong!"); | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -8,12 +8,6 @@ using Discord.WebSocket; | |||||
| class Program | class Program | ||||
| { | { | ||||
| private readonly DiscordSocketClient _client; | |||||
| // Keep the CommandService and IServiceCollection around for use with commands. | |||||
| private readonly IServiceCollection _map = new ServiceCollection(); | |||||
| private readonly CommandService _commands = new CommandService(); | |||||
| // Program entry point | // Program entry point | ||||
| static void Main(string[] args) | static void Main(string[] args) | ||||
| { | { | ||||
| @@ -22,6 +16,13 @@ class Program | |||||
| new Program().MainAsync().GetAwaiter().GetResult(); | new Program().MainAsync().GetAwaiter().GetResult(); | ||||
| } | } | ||||
| private readonly DiscordSocketClient _client; | |||||
| // Keep the CommandService and IServiceCollection around for use with commands. | |||||
| // These two types require you install the Discord.Net.Commands package. | |||||
| private readonly IServiceCollection _map = new ServiceCollection(); | |||||
| private readonly CommandService _commands = new CommandService(); | |||||
| private Program() | private Program() | ||||
| { | { | ||||
| _client = new DiscordSocketClient(new DiscordSocketConfig | _client = new DiscordSocketClient(new DiscordSocketConfig | ||||
| @@ -48,7 +49,6 @@ class Program | |||||
| // that ask for a Func<LogMessage, Task>. | // that ask for a Func<LogMessage, Task>. | ||||
| private static Task Logger(LogMessage message) | private static Task Logger(LogMessage message) | ||||
| { | { | ||||
| var cc = Console.ForegroundColor; | |||||
| switch (message.Severity) | switch (message.Severity) | ||||
| { | { | ||||
| case LogSeverity.Critical: | case LogSeverity.Critical: | ||||
| @@ -66,8 +66,8 @@ class Program | |||||
| Console.ForegroundColor = ConsoleColor.DarkGray; | Console.ForegroundColor = ConsoleColor.DarkGray; | ||||
| break; | break; | ||||
| } | } | ||||
| Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message}"); | |||||
| Console.ForegroundColor = cc; | |||||
| Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message} {message.Exception}"); | |||||
| Console.ResetColor(); | |||||
| // If you get an error saying 'CompletedTask' doesn't exist, | // If you get an error saying 'CompletedTask' doesn't exist, | ||||
| // your project is targeting .NET 4.5.2 or lower. You'll need | // your project is targeting .NET 4.5.2 or lower. You'll need | ||||
| @@ -105,10 +105,11 @@ class Program | |||||
| _services = _map.BuildServiceProvider(); | _services = _map.BuildServiceProvider(); | ||||
| // Either search the program and add all Module classes that can be found. | // Either search the program and add all Module classes that can be found. | ||||
| // Module classes *must* be marked 'public' or they will be ignored. | |||||
| // Module classes MUST be marked 'public' or they will be ignored. | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | ||||
| // Or add Modules manually if you prefer to be a little more explicit: | // Or add Modules manually if you prefer to be a little more explicit: | ||||
| await _commands.AddModuleAsync<SomeModule>(); | await _commands.AddModuleAsync<SomeModule>(); | ||||
| // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). | |||||
| // Subscribe a handler to see if a message invokes a command. | // Subscribe a handler to see if a message invokes a command. | ||||
| _client.MessageReceived += HandleCommandAsync; | _client.MessageReceived += HandleCommandAsync; | ||||
| @@ -120,6 +121,11 @@ class Program | |||||
| var msg = arg as SocketUserMessage; | var msg = arg as SocketUserMessage; | ||||
| if (msg == null) return; | if (msg == null) return; | ||||
| // We don't want the bot to respond to itself or other bots. | |||||
| // NOTE: Selfbots should invert this first check and remove the second | |||||
| // as they should ONLY be allowed to respond to messages from the same account. | |||||
| if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return; | |||||
| // Create a number to track where the prefix ends and the command begins | // Create a number to track where the prefix ends and the command begins | ||||
| int pos = 0; | int pos = 0; | ||||
| // Replace the '!' with whatever character | // Replace the '!' with whatever character | ||||
| @@ -7,32 +7,34 @@ title: Terminology | |||||
| ## Preface | ## Preface | ||||
| Most terms for objects remain the same between 0.9 and 1.0. The major difference is that the ``Server`` is now called ``Guild``, to stay in line with Discord internally | |||||
| Most terms for objects remain the same between 0.9 and 1.0. The major | |||||
| difference is that the ``Server`` is now called ``Guild`` to stay in | |||||
| line with Discord internally. | |||||
| ## Implementation Specific Entities | ## Implementation Specific Entities | ||||
| Discord.Net 1.0 is split into a core library, and three different | |||||
| implementations - Discord.Net.Core, Discord.Net.Rest, Discord.Net.Rpc, | |||||
| and Discord.Net.WebSockets. | |||||
| Discord.Net 1.0 is split into a core library and three different | |||||
| implementations - `Discord.Net.Core`, `Discord.Net.Rest`, | |||||
| `Discord.Net.Rpc`, and `Discord.Net.WebSockets`. | |||||
| As a bot developer, you will only need to use Discord.Net.WebSockets, | |||||
| As a bot developer, you will only need to use `Discord.Net.WebSockets`, | |||||
| but you should be aware of the differences between them. | but you should be aware of the differences between them. | ||||
| `Discord.Net.Core` provides a set of interfaces that model Discord's | |||||
| `Discord.Net.Core` provides a set of interfaces that models Discord's | |||||
| API. These interfaces are consistent throughout all implementations of | API. These interfaces are consistent throughout all implementations of | ||||
| Discord.Net, and if you are writing an implementation-agnostic library | Discord.Net, and if you are writing an implementation-agnostic library | ||||
| or addon, you can rely on the core interfaces to ensure that your | or addon, you can rely on the core interfaces to ensure that your | ||||
| addon will run on all platforms. | addon will run on all platforms. | ||||
| `Discord.Net.Rest` provides a set of concrete classes to be used | `Discord.Net.Rest` provides a set of concrete classes to be used | ||||
| **strictly** with the REST portion of Discord's API. Entities in | |||||
| this implementation are prefixed with `Rest`, e.g. `RestChannel`. | |||||
| **strictly** with the REST portion of Discord's API. Entities in this | |||||
| implementation are prefixed with `Rest` (e.g. `RestChannel`). | |||||
| `Discord.Net.Rpc` provides a set of concrete classes that are used with | |||||
| Discord's RPC API. Entities in this implementation are prefixed with | |||||
| `Rpc`, e.g. `RpcChannel`. | |||||
| `Discord.Net.Rpc` provides a set of concrete classes that are used | |||||
| with Discord's RPC API. Entities in this implementation are prefixed | |||||
| with `Rpc` (e.g. `RpcChannel`). | |||||
| `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`. | |||||
| `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`). | |||||
| @@ -17,7 +17,7 @@ when developing on .NET Core, this is where you execute `dotnet run` | |||||
| from; typically the same directory as your csproj). | from; typically the same directory as your csproj). | ||||
| For Windows Users, precompiled binaries are available for your | For Windows Users, precompiled binaries are available for your | ||||
| convienence [here](https://discord.foxbot.me/binaries/) | |||||
| convienence [here](https://discord.foxbot.me/binaries/). | |||||
| For Linux Users, you will need to compile [Sodium] and [Opus] from | For Linux Users, you will need to compile [Sodium] and [Opus] from | ||||
| source, or install them from your package manager. | source, or install them from your package manager. | ||||
| @@ -31,7 +31,7 @@ Joining a channel is the first step to sending audio, and will return | |||||
| an [IAudioClient] to send data with. | an [IAudioClient] to send data with. | ||||
| To join a channel, simply await [ConnectAsync] on any instance of an | To join a channel, simply await [ConnectAsync] on any instance of an | ||||
| @Discord.IVoiceChannel. | |||||
| @Discord.IAudioChannel. | |||||
| [!code-csharp[Joining a Channel](samples/joining_audio.cs)] | [!code-csharp[Joining a Channel](samples/joining_audio.cs)] | ||||
| @@ -44,7 +44,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on | |||||
| another voice channel in the guild. | another voice channel in the guild. | ||||
| [IAudioClient]: xref:Discord.Audio.IAudioClient | [IAudioClient]: xref:Discord.Audio.IAudioClient | ||||
| [ConnectAsync]: xref:Discord.IVoiceChannel#Discord_IVoiceChannel_ConnectAsync | |||||
| [ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__ | |||||
| ## Transmitting Audio | ## Transmitting Audio | ||||
| @@ -84,7 +84,7 @@ Channels should be left at `2`, unless you specified a different value | |||||
| for `-ac 2` when creating FFmpeg. | for `-ac 2` when creating FFmpeg. | ||||
| [AudioOutStream]: xref:Discord.Audio.AudioOutStream | [AudioOutStream]: xref:Discord.Audio.AudioOutStream | ||||
| [IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreatePCMStream_System_Int32_System_Int32_System_Nullable_System_Int32__System_Int32_ | |||||
| [IAudioClient.CreatePCMStream]: xref:Discord.Audio.IAudioClient#Discord_Audio_IAudioClient_CreateDirectPCMStream_Discord_Audio_AudioApplication_System_Nullable_System_Int32__System_Int32_ | |||||
| Finally, audio will need to be piped from FFmpeg's stdout into your | Finally, audio will need to be piped from FFmpeg's stdout into your | ||||
| AudioOutStream. This step can be as complex as you'd like it to be, but | AudioOutStream. This step can be as complex as you'd like it to be, but | ||||
| @@ -2,17 +2,18 @@ | |||||
| { | { | ||||
| public class CommandServiceConfig | public class CommandServiceConfig | ||||
| { | { | ||||
| /// <summary> The default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary> | |||||
| /// <summary> Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary> | |||||
| public RunMode DefaultRunMode { get; set; } = RunMode.Sync; | public RunMode DefaultRunMode { get; set; } = RunMode.Sync; | ||||
| public char SeparatorChar { get; set; } = ' '; | public char SeparatorChar { get; set; } = ' '; | ||||
| /// <summary> Should commands be case-sensitive? </summary> | |||||
| /// <summary> Determines whether commands should be case-sensitive. </summary> | |||||
| public bool CaseSensitiveCommands { get; set; } = false; | public bool CaseSensitiveCommands { get; set; } = false; | ||||
| /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | /// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary> | ||||
| public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | ||||
| /// <summary> Gets or sets whether RunMode.Sync commands should push exceptions up to the caller. </summary> | |||||
| /// <summary> Determines whether RunMode.Sync commands should push exceptions up to the caller. </summary> | |||||
| public bool ThrowOnError { get; set; } = true; | public bool ThrowOnError { get; set; } = true; | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -29,10 +29,6 @@ namespace Discord | |||||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | ||||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | /// <summary> Gets a collection of pinned messages in this channel. </summary> | ||||
| Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | Task<IReadOnlyCollection<IMessage>> GetPinnedMessagesAsync(RequestOptions options = null); | ||||
| /// <summary> Bulk deletes multiple messages. </summary> | |||||
| Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null); | |||||
| /// <summary> Bulk deletes multiple messages. </summary> | |||||
| Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null); | |||||
| /// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. </summary> | /// <summary> Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. </summary> | ||||
| Task TriggerTypingAsync(RequestOptions options = null); | Task TriggerTypingAsync(RequestOptions options = null); | ||||
| @@ -13,6 +13,11 @@ namespace Discord | |||||
| /// <summary> Gets the current topic for this text channel. </summary> | /// <summary> Gets the current topic for this text channel. </summary> | ||||
| string Topic { get; } | string Topic { get; } | ||||
| /// <summary> Bulk deletes multiple messages. </summary> | |||||
| Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null); | |||||
| /// <summary> Bulk deletes multiple messages. </summary> | |||||
| Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null); | |||||
| /// <summary> Modifies this text channel. </summary> | /// <summary> Modifies this text channel. </summary> | ||||
| Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); | Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); | ||||
| @@ -1,40 +1,36 @@ | |||||
| namespace Discord | |||||
| using System; | |||||
| namespace Discord | |||||
| { | { | ||||
| public enum ChannelPermission : byte | |||||
| [FlagsAttribute] | |||||
| public enum ChannelPermission : ulong | |||||
| { | { | ||||
| //General | |||||
| CreateInstantInvite = 0, | |||||
| //KickMembers = 1, | |||||
| //BanMembers = 2, | |||||
| //Administrator = 3, | |||||
| ManageChannel = 4, | |||||
| //ManageGuild = 5, | |||||
| // General | |||||
| CreateInstantInvite = 0x00_00_00_01, | |||||
| ManageChannels = 0x00_00_00_10, | |||||
| //Text | |||||
| AddReactions = 6, | |||||
| ReadMessages = 10, | |||||
| SendMessages = 11, | |||||
| SendTTSMessages = 12, | |||||
| ManageMessages = 13, | |||||
| EmbedLinks = 14, | |||||
| AttachFiles = 15, | |||||
| ReadMessageHistory = 16, | |||||
| MentionEveryone = 17, | |||||
| UseExternalEmojis = 18, | |||||
| // Text | |||||
| AddReactions = 0x00_00_00_40, | |||||
| ReadMessages = 0x00_00_04_00, | |||||
| SendMessages = 0x00_00_08_00, | |||||
| SendTTSMessages = 0x00_00_10_00, | |||||
| ManageMessages = 0x00_00_20_00, | |||||
| EmbedLinks = 0x00_00_40_00, | |||||
| AttachFiles = 0x00_00_80_00, | |||||
| ReadMessageHistory = 0x00_01_00_00, | |||||
| MentionEveryone = 0x00_02_00_00, | |||||
| UseExternalEmojis = 0x00_04_00_00, | |||||
| //Voice | |||||
| Connect = 20, | |||||
| Speak = 21, | |||||
| MuteMembers = 22, | |||||
| DeafenMembers = 23, | |||||
| MoveMembers = 24, | |||||
| UseVAD = 25, | |||||
| // Voice | |||||
| Connect = 0x00_10_00_00, | |||||
| Speak = 0x00_20_00_00, | |||||
| MuteMembers = 0x00_40_00_00, | |||||
| DeafenMembers = 0x00_80_00_00, | |||||
| MoveMembers = 0x01_00_00_00, | |||||
| UseVAD = 0x02_00_00_00, | |||||
| //General2 | |||||
| //ChangeNickname = 26, | |||||
| //ManageNicknames = 27, | |||||
| ManagePermissions = 28, | |||||
| ManageWebhooks = 29, | |||||
| //ManageEmojis = 30 | |||||
| // More General | |||||
| ManageRoles = 0x10_00_00_00, | |||||
| ManageWebhooks = 0x20_00_00_00, | |||||
| } | } | ||||
| } | } | ||||
| @@ -36,7 +36,7 @@ namespace Discord | |||||
| /// <summary> If True, a user may create invites. </summary> | /// <summary> If True, a user may create invites. </summary> | ||||
| public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); | public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); | ||||
| /// <summary> If True, a user may create, delete and modify this channel. </summary> | /// <summary> If True, a user may create, delete and modify this channel. </summary> | ||||
| public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel); | |||||
| public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); | |||||
| /// <summary> If true, a user may add reactions. </summary> | /// <summary> If true, a user may add reactions. </summary> | ||||
| public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | ||||
| @@ -72,8 +72,8 @@ namespace Discord | |||||
| /// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary> | /// <summary> If True, a user may use voice-activity-detection rather than push-to-talk. </summary> | ||||
| public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); | public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); | ||||
| /// <summary> If True, a user may adjust permissions. This also implictly grants all other permissions. </summary> | |||||
| public bool ManagePermissions => Permissions.GetValue(RawValue, ChannelPermission.ManagePermissions); | |||||
| /// <summary> If True, a user may adjust role permissions. This also implictly grants all other permissions. </summary> | |||||
| public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); | |||||
| /// <summary> If True, a user may edit the webhooks for this channel. </summary> | /// <summary> If True, a user may edit the webhooks for this channel. </summary> | ||||
| public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); | public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); | ||||
| @@ -85,12 +85,12 @@ namespace Discord | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | ||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | ||||
| bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | ||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) | |||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) | |||||
| { | { | ||||
| ulong value = initialValue; | ulong value = initialValue; | ||||
| Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); | Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); | ||||
| Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannel); | |||||
| Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannels); | |||||
| Permissions.SetValue(ref value, addReactions, ChannelPermission.AddReactions); | Permissions.SetValue(ref value, addReactions, ChannelPermission.AddReactions); | ||||
| Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); | Permissions.SetValue(ref value, readMessages, ChannelPermission.ReadMessages); | ||||
| Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); | Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); | ||||
| @@ -107,7 +107,7 @@ namespace Discord | |||||
| Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); | Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); | ||||
| Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); | Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); | ||||
| Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); | Permissions.SetValue(ref value, useVoiceActivation, ChannelPermission.UseVAD); | ||||
| Permissions.SetValue(ref value, managePermissions, ChannelPermission.ManagePermissions); | |||||
| Permissions.SetValue(ref value, manageRoles, ChannelPermission.ManageRoles); | |||||
| Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); | Permissions.SetValue(ref value, manageWebhooks, ChannelPermission.ManageWebhooks); | ||||
| RawValue = value; | RawValue = value; | ||||
| @@ -119,10 +119,10 @@ namespace Discord | |||||
| bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | ||||
| bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | ||||
| bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | ||||
| bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false, bool manageWebhooks = false) | |||||
| bool moveMembers = false, bool useVoiceActivation = false, bool manageRoles = false, bool manageWebhooks = false) | |||||
| : this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | : this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | ||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | ||||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks) | |||||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks) | |||||
| { } | { } | ||||
| /// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | /// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | ||||
| @@ -131,21 +131,21 @@ namespace Discord | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | ||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | ||||
| bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | ||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) | |||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? manageRoles = null, bool? manageWebhooks = null) | |||||
| => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | ||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | ||||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks); | |||||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); | |||||
| public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); | public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); | ||||
| public List<ChannelPermission> ToList() | public List<ChannelPermission> ToList() | ||||
| { | { | ||||
| var perms = new List<ChannelPermission>(); | var perms = new List<ChannelPermission>(); | ||||
| ulong x = 1; | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++) | |||||
| { | { | ||||
| if ((RawValue & x) != 0) | |||||
| perms.Add((ChannelPermission)i); | |||||
| ulong flag = ((ulong)1 << i); | |||||
| if ((RawValue & flag) != 0) | |||||
| perms.Add((ChannelPermission)flag); | |||||
| } | } | ||||
| return perms; | return perms; | ||||
| } | } | ||||
| @@ -1,40 +1,44 @@ | |||||
| namespace Discord | |||||
| using System; | |||||
| namespace Discord | |||||
| { | { | ||||
| public enum GuildPermission : byte | |||||
| [FlagsAttribute] | |||||
| public enum GuildPermission : ulong | |||||
| { | { | ||||
| //General | |||||
| CreateInstantInvite = 0, | |||||
| KickMembers = 1, | |||||
| BanMembers = 2, | |||||
| Administrator = 3, | |||||
| ManageChannels = 4, | |||||
| ManageGuild = 5, | |||||
| // General | |||||
| CreateInstantInvite = 0x00_00_00_01, | |||||
| KickMembers = 0x00_00_00_02, | |||||
| BanMembers = 0x00_00_00_04, | |||||
| Administrator = 0x00_00_00_08, | |||||
| ManageChannels = 0x00_00_00_10, | |||||
| ManageGuild = 0x00_00_00_20, | |||||
| //Text | |||||
| AddReactions = 6, | |||||
| ReadMessages = 10, | |||||
| SendMessages = 11, | |||||
| SendTTSMessages = 12, | |||||
| ManageMessages = 13, | |||||
| EmbedLinks = 14, | |||||
| AttachFiles = 15, | |||||
| ReadMessageHistory = 16, | |||||
| MentionEveryone = 17, | |||||
| UseExternalEmojis = 18, | |||||
| // Text | |||||
| AddReactions = 0x00_00_00_40, | |||||
| ViewAuditLog = 0x00_00_00_80, | |||||
| ReadMessages = 0x00_00_04_00, | |||||
| SendMessages = 0x00_00_08_00, | |||||
| SendTTSMessages = 0x00_00_10_00, | |||||
| ManageMessages = 0x00_00_20_00, | |||||
| EmbedLinks = 0x00_00_40_00, | |||||
| AttachFiles = 0x00_00_80_00, | |||||
| ReadMessageHistory = 0x00_01_00_00, | |||||
| MentionEveryone = 0x00_02_00_00, | |||||
| UseExternalEmojis = 0x00_04_00_00, | |||||
| //Voice | |||||
| Connect = 20, | |||||
| Speak = 21, | |||||
| MuteMembers = 22, | |||||
| DeafenMembers = 23, | |||||
| MoveMembers = 24, | |||||
| UseVAD = 25, | |||||
| // Voice | |||||
| Connect = 0x00_10_00_00, | |||||
| Speak = 0x00_20_00_00, | |||||
| MuteMembers = 0x00_40_00_00, | |||||
| DeafenMembers = 0x00_80_00_00, | |||||
| MoveMembers = 0x01_00_00_00, | |||||
| UseVAD = 0x02_00_00_00, | |||||
| //General2 | |||||
| ChangeNickname = 26, | |||||
| ManageNicknames = 27, | |||||
| ManageRoles = 28, | |||||
| ManageWebhooks = 29, | |||||
| ManageEmojis = 30 | |||||
| // General 2 | |||||
| ChangeNickname = 0x04_00_00_00, | |||||
| ManageNicknames = 0x08_00_00_00, | |||||
| ManageRoles = 0x10_00_00_00, | |||||
| ManageWebhooks = 0x20_00_00_00, | |||||
| ManageEmojis = 0x40_00_00_00 | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,7 +11,7 @@ namespace Discord | |||||
| /// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary> | /// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary> | ||||
| public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | ||||
| /// <summary> Gets a GuildPermissions that grants all guild permissions. </summary> | /// <summary> Gets a GuildPermissions that grants all guild permissions. </summary> | ||||
| public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110001_111111); | |||||
| public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110011_111111); | |||||
| /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | ||||
| public ulong RawValue { get; } | public ulong RawValue { get; } | ||||
| @@ -31,6 +31,9 @@ namespace Discord | |||||
| /// <summary> If true, a user may add reactions. </summary> | /// <summary> If true, a user may add reactions. </summary> | ||||
| public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); | public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); | ||||
| /// <summary> If true, a user may view the audit log. </summary> | |||||
| public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); | |||||
| /// <summary> If True, a user may join channels. </summary> | /// <summary> If True, a user may join channels. </summary> | ||||
| public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); | public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); | ||||
| /// <summary> If True, a user may send messages. </summary> | /// <summary> If True, a user may send messages. </summary> | ||||
| @@ -78,11 +81,11 @@ namespace Discord | |||||
| public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | ||||
| private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, | private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = null, | ||||
| bool? banMembers = null, bool? administrator = null, bool? manageChannel = null, bool? manageGuild = null, | |||||
| bool? addReactions = null, | |||||
| bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, | |||||
| bool? addReactions = null, bool? viewAuditLog = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | ||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | ||||
| bool? userExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | |||||
| bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | |||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | ||||
| bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) | bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) | ||||
| { | { | ||||
| @@ -92,9 +95,10 @@ namespace Discord | |||||
| Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); | Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); | ||||
| Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); | Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); | ||||
| Permissions.SetValue(ref value, administrator, GuildPermission.Administrator); | Permissions.SetValue(ref value, administrator, GuildPermission.Administrator); | ||||
| Permissions.SetValue(ref value, manageChannel, GuildPermission.ManageChannels); | |||||
| Permissions.SetValue(ref value, manageChannels, GuildPermission.ManageChannels); | |||||
| Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); | Permissions.SetValue(ref value, manageGuild, GuildPermission.ManageGuild); | ||||
| Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); | Permissions.SetValue(ref value, addReactions, GuildPermission.AddReactions); | ||||
| Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog); | |||||
| Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); | Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); | ||||
| Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); | Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); | ||||
| Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); | Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); | ||||
| @@ -103,7 +107,7 @@ namespace Discord | |||||
| Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); | Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); | ||||
| Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); | Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); | ||||
| Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); | Permissions.SetValue(ref value, mentionEveryone, GuildPermission.MentionEveryone); | ||||
| Permissions.SetValue(ref value, userExternalEmojis, GuildPermission.UseExternalEmojis); | |||||
| Permissions.SetValue(ref value, useExternalEmojis, GuildPermission.UseExternalEmojis); | |||||
| Permissions.SetValue(ref value, connect, GuildPermission.Connect); | Permissions.SetValue(ref value, connect, GuildPermission.Connect); | ||||
| Permissions.SetValue(ref value, speak, GuildPermission.Speak); | Permissions.SetValue(ref value, speak, GuildPermission.Speak); | ||||
| Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); | Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); | ||||
| @@ -122,26 +126,26 @@ namespace Discord | |||||
| /// <summary> Creates a new GuildPermissions with the provided permissions. </summary> | /// <summary> Creates a new GuildPermissions with the provided permissions. </summary> | ||||
| public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, | public GuildPermissions(bool createInstantInvite = false, bool kickMembers = false, | ||||
| bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, | bool banMembers = false, bool administrator = false, bool manageChannels = false, bool manageGuild = false, | ||||
| bool addReactions = false, | |||||
| bool addReactions = false, bool viewAuditLog = false, | |||||
| bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | ||||
| bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | ||||
| bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | ||||
| bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, | bool moveMembers = false, bool useVoiceActivation = false, bool? changeNickname = false, bool? manageNicknames = false, | ||||
| bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) | bool manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) | ||||
| : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, | |||||
| : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, | |||||
| readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | ||||
| manageWebhooks, manageEmojis) { } | manageWebhooks, manageEmojis) { } | ||||
| /// <summary> Creates a new GuildPermissions from this one, changing the provided non-null permissions. </summary> | /// <summary> Creates a new GuildPermissions from this one, changing the provided non-null permissions. </summary> | ||||
| public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, | public GuildPermissions Modify(bool? createInstantInvite = null, bool? kickMembers = null, | ||||
| bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, | bool? banMembers = null, bool? administrator = null, bool? manageChannels = null, bool? manageGuild = null, | ||||
| bool? addReactions = null, | |||||
| bool? addReactions = null, bool? viewAuditLog = null, | |||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | ||||
| bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | ||||
| bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | ||||
| bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | ||||
| bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) | bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) | ||||
| => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, | |||||
| => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, | |||||
| readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | ||||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, | speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, | ||||
| manageWebhooks, manageEmojis); | manageWebhooks, manageEmojis); | ||||
| @@ -151,11 +155,14 @@ namespace Discord | |||||
| public List<GuildPermission> ToList() | public List<GuildPermission> ToList() | ||||
| { | { | ||||
| var perms = new List<GuildPermission>(); | var perms = new List<GuildPermission>(); | ||||
| ulong x = 1; | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) | |||||
| // bitwise operations on raw value | |||||
| // each of the GuildPermissions increments by 2^i from 0 to MaxBits | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++) | |||||
| { | { | ||||
| if ((RawValue & x) != 0) | |||||
| perms.Add((GuildPermission)i); | |||||
| ulong flag = ((ulong)1 << i); | |||||
| if ((RawValue & flag) != 0) | |||||
| perms.Add((GuildPermission)flag); | |||||
| } | } | ||||
| return perms; | return perms; | ||||
| } | } | ||||
| @@ -23,7 +23,7 @@ namespace Discord | |||||
| /// <summary> If Allowed, a user may create invites. </summary> | /// <summary> If Allowed, a user may create invites. </summary> | ||||
| public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); | public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); | ||||
| /// <summary> If Allowed, a user may create, delete and modify this channel. </summary> | /// <summary> If Allowed, a user may create, delete and modify this channel. </summary> | ||||
| public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel); | |||||
| public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannels); | |||||
| /// <summary> If Allowed, a user may add reactions. </summary> | /// <summary> If Allowed, a user may add reactions. </summary> | ||||
| public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | ||||
| /// <summary> If Allowed, a user may join channels. </summary> | /// <summary> If Allowed, a user may join channels. </summary> | ||||
| @@ -58,8 +58,8 @@ namespace Discord | |||||
| /// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary> | /// <summary> If Allowed, a user may use voice-activity-detection rather than push-to-talk. </summary> | ||||
| public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); | public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); | ||||
| /// <summary> If Allowed, a user may adjust permissions. This also implictly grants all other permissions. </summary> | |||||
| public PermValue ManagePermissions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions); | |||||
| /// <summary> If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. </summary> | |||||
| public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); | |||||
| /// <summary> If True, a user may edit the webhooks for this channel. </summary> | /// <summary> If True, a user may edit the webhooks for this channel. </summary> | ||||
| public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); | public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); | ||||
| @@ -75,11 +75,11 @@ namespace Discord | |||||
| PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, | PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, | ||||
| PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, | PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, | ||||
| PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, | PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, | ||||
| PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, | |||||
| PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, | |||||
| PermValue? manageWebhooks = null) | PermValue? manageWebhooks = null) | ||||
| { | { | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); | Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannel); | |||||
| Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels); | |||||
| Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions); | Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); | Permissions.SetValue(ref allowValue, ref denyValue, readMessages, ChannelPermission.ReadMessages); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); | Permissions.SetValue(ref allowValue, ref denyValue, sendMessages, ChannelPermission.SendMessages); | ||||
| @@ -96,7 +96,7 @@ namespace Discord | |||||
| Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); | Permissions.SetValue(ref allowValue, ref denyValue, deafenMembers, ChannelPermission.DeafenMembers); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); | Permissions.SetValue(ref allowValue, ref denyValue, moveMembers, ChannelPermission.MoveMembers); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); | Permissions.SetValue(ref allowValue, ref denyValue, useVoiceActivation, ChannelPermission.UseVAD); | ||||
| Permissions.SetValue(ref allowValue, ref denyValue, managePermissions, ChannelPermission.ManagePermissions); | |||||
| Permissions.SetValue(ref allowValue, ref denyValue, manageRoles, ChannelPermission.ManageRoles); | |||||
| Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); | Permissions.SetValue(ref allowValue, ref denyValue, manageWebhooks, ChannelPermission.ManageWebhooks); | ||||
| AllowValue = allowValue; | AllowValue = allowValue; | ||||
| @@ -109,10 +109,10 @@ namespace Discord | |||||
| PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, | PermValue readMessages = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, PermValue manageMessages = PermValue.Inherit, | ||||
| PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit, | PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, PermValue mentionEveryone = PermValue.Inherit, | ||||
| PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit, | PermValue useExternalEmojis = PermValue.Inherit, PermValue connect = PermValue.Inherit, PermValue speak = PermValue.Inherit, PermValue muteMembers = PermValue.Inherit, PermValue deafenMembers = PermValue.Inherit, | ||||
| PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue managePermissions = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) | |||||
| PermValue moveMembers = PermValue.Inherit, PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) | |||||
| : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | : this(0, 0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | ||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, | embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, | ||||
| moveMembers, useVoiceActivation, managePermissions, manageWebhooks) { } | |||||
| moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } | |||||
| /// <summary> Creates a new OverwritePermissions from this one, changing the provided non-null permissions. </summary> | /// <summary> Creates a new OverwritePermissions from this one, changing the provided non-null permissions. </summary> | ||||
| public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null, | public OverwritePermissions Modify(PermValue? createInstantInvite = null, PermValue? manageChannel = null, | ||||
| @@ -120,30 +120,31 @@ namespace Discord | |||||
| PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, | PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, PermValue? manageMessages = null, | ||||
| PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, | PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, PermValue? mentionEveryone = null, | ||||
| PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, | ||||
| PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, PermValue? manageWebhooks = null) | |||||
| PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null) | |||||
| => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | ||||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, | embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, | ||||
| moveMembers, useVoiceActivation, managePermissions, manageWebhooks); | |||||
| moveMembers, useVoiceActivation, manageRoles, manageWebhooks); | |||||
| public List<ChannelPermission> ToAllowList() | public List<ChannelPermission> ToAllowList() | ||||
| { | { | ||||
| var perms = new List<ChannelPermission>(); | var perms = new List<ChannelPermission>(); | ||||
| ulong x = 1; | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++) | |||||
| { | { | ||||
| if ((AllowValue & x) != 0) | |||||
| perms.Add((ChannelPermission)i); | |||||
| // first operand must be long or ulong to shift >31 bits | |||||
| ulong flag = ((ulong)1 << i); | |||||
| if ((AllowValue & flag) != 0) | |||||
| perms.Add((ChannelPermission)flag); | |||||
| } | } | ||||
| return perms; | return perms; | ||||
| } | } | ||||
| public List<ChannelPermission> ToDenyList() | public List<ChannelPermission> ToDenyList() | ||||
| { | { | ||||
| var perms = new List<ChannelPermission>(); | var perms = new List<ChannelPermission>(); | ||||
| ulong x = 1; | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++, x <<= 1) | |||||
| for (byte i = 0; i < Permissions.MaxBits; i++) | |||||
| { | { | ||||
| if ((DenyValue & x) != 0) | |||||
| perms.Add((ChannelPermission)i); | |||||
| ulong flag = ((ulong)1 << i); | |||||
| if ((DenyValue & flag) != 0) | |||||
| perms.Add((ChannelPermission)flag); | |||||
| } | } | ||||
| return perms; | return perms; | ||||
| } | } | ||||
| @@ -7,84 +7,84 @@ namespace Discord | |||||
| public const int MaxBits = 53; | public const int MaxBits = 53; | ||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission bit) | |||||
| => GetValue(allow, deny, (byte)bit); | |||||
| public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission flag) | |||||
| => GetValue(allow, deny, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static PermValue GetValue(ulong allow, ulong deny, GuildPermission bit) | |||||
| => GetValue(allow, deny, (byte)bit); | |||||
| public static PermValue GetValue(ulong allow, ulong deny, GuildPermission flag) | |||||
| => GetValue(allow, deny, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static PermValue GetValue(ulong allow, ulong deny, byte bit) | |||||
| public static PermValue GetValue(ulong allow, ulong deny, ulong flag) | |||||
| { | { | ||||
| if (HasBit(allow, bit)) | |||||
| if (HasFlag(allow, flag)) | |||||
| return PermValue.Allow; | return PermValue.Allow; | ||||
| else if (HasBit(deny, bit)) | |||||
| else if (HasFlag(deny, flag)) | |||||
| return PermValue.Deny; | return PermValue.Deny; | ||||
| else | else | ||||
| return PermValue.Inherit; | return PermValue.Inherit; | ||||
| } | } | ||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static bool GetValue(ulong value, ChannelPermission bit) | |||||
| => GetValue(value, (byte)bit); | |||||
| public static bool GetValue(ulong value, ChannelPermission flag) | |||||
| => GetValue(value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static bool GetValue(ulong value, GuildPermission bit) | |||||
| => GetValue(value, (byte)bit); | |||||
| public static bool GetValue(ulong value, GuildPermission flag) | |||||
| => GetValue(value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static bool GetValue(ulong value, byte bit) => HasBit(value, bit); | |||||
| public static bool GetValue(ulong value, ulong flag) => HasFlag(value, flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission bit) | |||||
| => SetValue(ref rawValue, value, (byte)bit); | |||||
| public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission flag) | |||||
| => SetValue(ref rawValue, value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong rawValue, bool? value, GuildPermission bit) | |||||
| => SetValue(ref rawValue, value, (byte)bit); | |||||
| public static void SetValue(ref ulong rawValue, bool? value, GuildPermission flag) | |||||
| => SetValue(ref rawValue, value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong rawValue, bool? value, byte bit) | |||||
| public static void SetValue(ref ulong rawValue, bool? value, ulong flag) | |||||
| { | { | ||||
| if (value.HasValue) | if (value.HasValue) | ||||
| { | { | ||||
| if (value == true) | if (value == true) | ||||
| SetBit(ref rawValue, bit); | |||||
| SetFlag(ref rawValue, flag); | |||||
| else | else | ||||
| UnsetBit(ref rawValue, bit); | |||||
| UnsetFlag(ref rawValue, flag); | |||||
| } | } | ||||
| } | } | ||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission bit) | |||||
| => SetValue(ref allow, ref deny, value, (byte)bit); | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission flag) | |||||
| => SetValue(ref allow, ref deny, value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission bit) | |||||
| => SetValue(ref allow, ref deny, value, (byte)bit); | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission flag) | |||||
| => SetValue(ref allow, ref deny, value, (ulong)flag); | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, byte bit) | |||||
| public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ulong flag) | |||||
| { | { | ||||
| if (value.HasValue) | if (value.HasValue) | ||||
| { | { | ||||
| switch (value) | switch (value) | ||||
| { | { | ||||
| case PermValue.Allow: | case PermValue.Allow: | ||||
| SetBit(ref allow, bit); | |||||
| UnsetBit(ref deny, bit); | |||||
| SetFlag(ref allow, flag); | |||||
| UnsetFlag(ref deny, flag); | |||||
| break; | break; | ||||
| case PermValue.Deny: | case PermValue.Deny: | ||||
| UnsetBit(ref allow, bit); | |||||
| SetBit(ref deny, bit); | |||||
| UnsetFlag(ref allow, flag); | |||||
| SetFlag(ref deny, flag); | |||||
| break; | break; | ||||
| default: | default: | ||||
| UnsetBit(ref allow, bit); | |||||
| UnsetBit(ref deny, bit); | |||||
| UnsetFlag(ref allow, flag); | |||||
| UnsetFlag(ref deny, flag); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| private static bool HasBit(ulong value, byte bit) => (value & (1U << bit)) != 0; | |||||
| private static bool HasFlag(ulong value, ulong flag) => (value & flag) != 0; | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void SetBit(ref ulong value, byte bit) => value |= (1U << bit); | |||||
| public static void SetFlag(ref ulong value, ulong flag) => value |= flag; | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); | |||||
| public static void UnsetFlag(ref ulong value, ulong flag) => value &= ~flag; | |||||
| public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) | public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) | ||||
| => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); | => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); | ||||
| @@ -182,7 +182,7 @@ namespace Discord.Rest | |||||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
| } | } | ||||
| public static async Task DeleteMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | |||||
| public static async Task DeleteMessagesAsync(ITextChannel channel, BaseDiscordClient client, | |||||
| IEnumerable<ulong> messageIds, RequestOptions options) | IEnumerable<ulong> messageIds, RequestOptions options) | ||||
| { | { | ||||
| var msgs = messageIds.ToArray(); | var msgs = messageIds.ToArray(); | ||||
| @@ -72,11 +72,6 @@ namespace Discord.Rest | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -85,11 +85,6 @@ namespace Discord.Rest | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -42,11 +42,6 @@ namespace Discord.Rest | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -53,11 +53,6 @@ namespace Discord.Rpc | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -56,11 +56,6 @@ namespace Discord.Rpc | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -13,8 +13,6 @@ namespace Discord.API.Gateway | |||||
| public IDictionary<string, string> Properties { get; set; } | public IDictionary<string, string> Properties { get; set; } | ||||
| [JsonProperty("large_threshold")] | [JsonProperty("large_threshold")] | ||||
| public int LargeThreshold { get; set; } | public int LargeThreshold { get; set; } | ||||
| [JsonProperty("compress")] | |||||
| public bool UseCompression { get; set; } | |||||
| [JsonProperty("shard")] | [JsonProperty("shard")] | ||||
| public Optional<int[]> ShardingParams { get; set; } | public Optional<int[]> ShardingParams { get; set; } | ||||
| } | } | ||||
| @@ -29,7 +29,11 @@ namespace Discord.API | |||||
| private CancellationTokenSource _connectCancelToken; | private CancellationTokenSource _connectCancelToken; | ||||
| private string _gatewayUrl; | private string _gatewayUrl; | ||||
| private bool _isExplicitUrl; | private bool _isExplicitUrl; | ||||
| //Store our decompression streams for zlib shared state | |||||
| private MemoryStream _compressed; | |||||
| private DeflateStream _decompressor; | |||||
| internal IWebSocketClient WebSocketClient { get; } | internal IWebSocketClient WebSocketClient { get; } | ||||
| public ConnectionState ConnectionState { get; private set; } | public ConnectionState ConnectionState { get; private set; } | ||||
| @@ -43,14 +47,29 @@ namespace Discord.API | |||||
| _isExplicitUrl = true; | _isExplicitUrl = true; | ||||
| WebSocketClient = webSocketProvider(); | WebSocketClient = webSocketProvider(); | ||||
| //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) | //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) | ||||
| WebSocketClient.BinaryMessage += async (data, index, count) => | WebSocketClient.BinaryMessage += async (data, index, count) => | ||||
| { | { | ||||
| using (var compressed = new MemoryStream(data, index + 2, count - 2)) | |||||
| using (var decompressed = new MemoryStream()) | using (var decompressed = new MemoryStream()) | ||||
| { | { | ||||
| using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) | |||||
| zlib.CopyTo(decompressed); | |||||
| if (data[0] == 0x78) | |||||
| { | |||||
| //Strip the zlib header | |||||
| _compressed.Write(data, index + 2, count - 2); | |||||
| _compressed.SetLength(count - 2); | |||||
| } | |||||
| else | |||||
| { | |||||
| _compressed.Write(data, index, count); | |||||
| _compressed.SetLength(count); | |||||
| } | |||||
| //Reset positions so we don't run out of memory | |||||
| _compressed.Position = 0; | |||||
| _decompressor.CopyTo(decompressed); | |||||
| _compressed.Position = 0; | |||||
| decompressed.Position = 0; | decompressed.Position = 0; | ||||
| using (var reader = new StreamReader(decompressed)) | using (var reader = new StreamReader(decompressed)) | ||||
| using (var jsonReader = new JsonTextReader(reader)) | using (var jsonReader = new JsonTextReader(reader)) | ||||
| { | { | ||||
| @@ -76,6 +95,7 @@ namespace Discord.API | |||||
| await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | ||||
| }; | }; | ||||
| } | } | ||||
| internal override void Dispose(bool disposing) | internal override void Dispose(bool disposing) | ||||
| { | { | ||||
| if (!_isDisposed) | if (!_isDisposed) | ||||
| @@ -84,6 +104,8 @@ namespace Discord.API | |||||
| { | { | ||||
| _connectCancelToken?.Dispose(); | _connectCancelToken?.Dispose(); | ||||
| (WebSocketClient as IDisposable)?.Dispose(); | (WebSocketClient as IDisposable)?.Dispose(); | ||||
| _decompressor?.Dispose(); | |||||
| _compressed?.Dispose(); | |||||
| } | } | ||||
| _isDisposed = true; | _isDisposed = true; | ||||
| } | } | ||||
| @@ -105,6 +127,12 @@ namespace Discord.API | |||||
| if (WebSocketClient == null) | if (WebSocketClient == null) | ||||
| throw new NotSupportedException("This client is not configured with websocket support."); | throw new NotSupportedException("This client is not configured with websocket support."); | ||||
| //Re-create streams to reset the zlib state | |||||
| _compressed?.Dispose(); | |||||
| _decompressor?.Dispose(); | |||||
| _compressed = new MemoryStream(); | |||||
| _decompressor = new DeflateStream(_compressed, CompressionMode.Decompress); | |||||
| ConnectionState = ConnectionState.Connecting; | ConnectionState = ConnectionState.Connecting; | ||||
| try | try | ||||
| { | { | ||||
| @@ -115,7 +143,7 @@ namespace Discord.API | |||||
| if (!_isExplicitUrl) | if (!_isExplicitUrl) | ||||
| { | { | ||||
| var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); | var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); | ||||
| _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}"; | |||||
| _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; | |||||
| } | } | ||||
| await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); | await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); | ||||
| @@ -191,7 +219,7 @@ namespace Discord.API | |||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| return await SendAsync<GetBotGatewayResponse>("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); | return await SendAsync<GetBotGatewayResponse>("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task SendIdentifyAsync(int largeThreshold = 100, bool useCompression = true, int shardID = 0, int totalShards = 1, RequestOptions options = null) | |||||
| public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null) | |||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| var props = new Dictionary<string, string> | var props = new Dictionary<string, string> | ||||
| @@ -202,8 +230,7 @@ namespace Discord.API | |||||
| { | { | ||||
| Token = AuthToken, | Token = AuthToken, | ||||
| Properties = props, | Properties = props, | ||||
| LargeThreshold = largeThreshold, | |||||
| UseCompression = useCompression, | |||||
| LargeThreshold = largeThreshold | |||||
| }; | }; | ||||
| if (totalShards > 1) | if (totalShards > 1) | ||||
| msg.ShardingParams = new int[] { shardID, totalShards }; | msg.ShardingParams = new int[] { shardID, totalShards }; | ||||
| @@ -76,11 +76,6 @@ namespace Discord.WebSocket | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| @@ -104,11 +104,6 @@ namespace Discord.WebSocket | |||||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, RequestOptions options = null) | ||||
| => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options); | ||||
| public Task DeleteMessagesAsync(IEnumerable<IMessage> messages, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); | |||||
| public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||||
| => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||||
| public Task TriggerTypingAsync(RequestOptions options = null) | public Task TriggerTypingAsync(RequestOptions options = null) | ||||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | => ChannelHelper.TriggerTypingAsync(this, Discord, options); | ||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||