| @@ -1,6 +1,6 @@ | |||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||
| # Visual Studio 15 | |||
| VisualStudioVersion = 15.0.26228.4 | |||
| VisualStudioVersion = 15.0.26730.12 | |||
| 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}" | |||
| EndProject | |||
| @@ -142,4 +142,10 @@ Global | |||
| {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012} | |||
| {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A} | |||
| EndGlobalSection | |||
| GlobalSection(ExtensibilityGlobals) = postSolution | |||
| SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495} | |||
| EndGlobalSection | |||
| GlobalSection(CodealikeProperties) = postSolution | |||
| SolutionGuid = a45217b4-a401-4dbf-8654-34d2ec034cd9 | |||
| EndGlobalSection | |||
| EndGlobal | |||
| @@ -1,7 +1,7 @@ | |||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <PropertyGroup> | |||
| <VersionPrefix>2.0.0-alpha</VersionPrefix> | |||
| <VersionSuffix></VersionSuffix> | |||
| <VersionPrefix>2.0.0</VersionPrefix> | |||
| <VersionSuffix>beta</VersionSuffix> | |||
| <Authors>RogueException</Authors> | |||
| <PackageTags>discord;discordapp</PackageTags> | |||
| <PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
| @@ -2,11 +2,11 @@ | |||
| [](https://www.nuget.org/packages/Discord.Net) | |||
| [](https://www.myget.org/feed/Packages/discord-net) | |||
| [](https://ci.appveyor.com/project/RogueException/discord-net/branch/dev) | |||
| [](https://discord.gg/0SBTUU1wZTVjAMPx) | |||
| [](https://discord.gg/jkrBmQR) | |||
| An unofficial .NET API Wrapper for the Discord client (http://discordapp.com). | |||
| Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). | |||
| Check out the [documentation](https://discord.foxbot.me/docs/) or join the [Discord API Chat](https://discord.gg/jkrBmQR). | |||
| ## Installation | |||
| ### Stable (NuGet) | |||
| @@ -34,7 +34,7 @@ after_build: | |||
| if ($Env:APPVEYOR_REPO_TAG -eq "true") { | |||
| nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" | |||
| } else { | |||
| nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-build-$Env:BUILD" | |||
| nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$Env:BUILD" | |||
| } | |||
| - ps: Get-ChildItem artifacts\*.nupkg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } | |||
| @@ -1,24 +1,20 @@ | |||
| # 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 | |||
| Command Parser. | |||
| command parser. | |||
| ## 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 | |||
| look over the properties in [CommandServiceConfig], and their default | |||
| look over the properties in [CommandServiceConfig] and their default | |||
| values. | |||
| [!code-csharp[Command Handler](samples/command_handler.cs)] | |||
| @@ -28,32 +24,32 @@ values. | |||
| ## 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 | |||
| 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 | |||
| 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. | |||
| 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 | |||
| 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] | |||
| >[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. | |||
| By now, your module should look like this: | |||
| [!code-csharp[Empty Module](samples/empty-module.cs)] | |||
| [IoC]: https://msdn.microsoft.com/en-us/library/ff921087.aspx | |||
| @@ -69,72 +66,75 @@ By now, your module should look like this: | |||
| ### 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. | |||
| 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 | |||
| optional, give it a default value. To accept a comma-separated list, | |||
| set the parameter to `params Type[]`. | |||
| 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"`. | |||
| 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 | |||
| 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 | |||
| [CommandAttribute]: xref:Discord.Commands.CommandAttribute | |||
| ### 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. | |||
| 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 | |||
| 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 | |||
| 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. | |||
| To reply to messages, you may also invoke [ReplyAsync], instead of | |||
| 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 | |||
| [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 | |||
| @@ -144,50 +144,50 @@ At this point, your module should look comparable to this example: | |||
| #### Loading Modules Automatically | |||
| 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 | |||
| [DontAutoLoadAttribute] | |||
| [DontAutoLoadAttribute]. | |||
| Invoke [CommandService.AddModulesAsync] to discover modules and | |||
| install them. | |||
| [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 | |||
| 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 | |||
| ### Module Constructors | |||
| 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 | |||
| 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 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 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 | |||
| create nested groups). | |||
| @@ -199,54 +199,62 @@ create nested groups). | |||
| ## 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 | |||
| 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)] | |||
| ### 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] | |||
| > 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] | |||
| >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)] | |||
| [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute | |||
| # 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] | |||
| >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 | |||
| 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.RequireOwnerAttribute | |||
| @@ -255,21 +263,23 @@ usages on their API page. | |||
| ## 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. | |||
| 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)] | |||
| [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.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 | |||
| 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] | |||
| >In many cases, Visual Studio can fill this in for you, using the | |||
| >"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 | |||
| @@ -319,5 +335,9 @@ Otherwise, return `TypeReaderResult.FromError`. | |||
| ### 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 | |||
| { | |||
| 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"; | |||
| 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); | |||
| } | |||
| public async Task InstallCommands() | |||
| public async Task InstallCommandsAsync() | |||
| { | |||
| // 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. | |||
| 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 | |||
| 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 | |||
| int argPos = 0; | |||
| // 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 | |||
| var context = new CommandContext(client, message); | |||
| var context = new SocketCommandContext(_client, message); | |||
| // Execute the command. (result does not indicate a return value, | |||
| // 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) | |||
| 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; | |||
| public class InfoModule : ModuleBase | |||
| public class InfoModule : ModuleBase<SocketCommandContext> | |||
| { | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| [Group("admin")] | |||
| public class AdminModule : ModuleBase | |||
| public class AdminModule : ModuleBase<SocketCommandContext> | |||
| { | |||
| [Group("clean")] | |||
| public class CleanModule : ModuleBase | |||
| public class CleanModule : ModuleBase<SocketCommandContext> | |||
| { | |||
| // ~admin clean 15 | |||
| [Command] | |||
| @@ -1,42 +1,41 @@ | |||
| using Discord; | |||
| using Discord.Commands; | |||
| using Discord.WebSocket; | |||
| // 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 | |||
| [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#8708 --> Khionu#8708 | |||
| // ~sample userinfo Khionu --> 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 | |||
| 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 | |||
| possible, even if the type is less detailed. | |||
| @@ -61,8 +61,11 @@ a variant of the type that you need. | |||
| ### Tips | |||
| 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 | |||
| 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. | |||
| 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 | |||
| To receive data from an event, hook into it using C#'s delegate | |||
| 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. | |||
| ### 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. | |||
| 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 | |||
| longer than three seconds should **not** be awaited directly in the | |||
| 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>` | |||
| 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. | |||
| See the documentation for [Cacheable] for more information on this | |||
| @@ -76,8 +76,8 @@ object. | |||
| ### 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 | |||
| [DiscordSocketConfig] to enable it. | |||
| @@ -14,12 +14,14 @@ section. | |||
| ### Usage | |||
| 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 | |||
| 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. | |||
| [LogMessage]: xref:Discord.LogMessage | |||
| ### Usage in Commands | |||
| 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 | |||
| and error can be found and handled. | |||
| [CommandService]: xref:Discord.Commands.CommandService | |||
| [CommandException]: xref:Discord.Commands.CommandException | |||
| #### Samples | |||
| [!code-csharp[Logging Sample](samples/logging.cs)] | |||
| @@ -2,84 +2,87 @@ | |||
| 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. | |||
| # 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 Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ | |||
| [additional steps]: #installing-on-net-standard-11 | |||
| # Installing with NuGet | |||
| Release builds of Discord.Net 1.0 will be published to the | |||
| [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` | |||
| 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 | |||
| [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 | |||
| [without Visual Studio]: #configuring-nuget-without-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] | |||
| 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 | |||
| 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] | |||
| 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 | |||
| 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)] | |||
| > [!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 | |||
| @@ -90,8 +93,8 @@ In order to compile Discord.Net, you require the following: | |||
| - [Visual Studio 2017](https://www.visualstudio.com/) | |||
| - [.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 | |||
| @@ -101,26 +104,27 @@ installation. | |||
| ## 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.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)] | |||
| @@ -129,13 +133,14 @@ client. | |||
| ## 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)] | |||
| @@ -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 | |||
| 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 | |||
| 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 | |||
| ## 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. | |||
| 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**. | |||
|  | |||
|  | |||
| 3. Create an OAuth2 authorization URL | |||
| `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 | |||
| @@ -57,10 +61,10 @@ do that now. (see the [Installing](installing.md) section) | |||
| ### 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. | |||
| 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 | |||
| async context can be problematic, but not hard. | |||
| @@ -70,27 +74,29 @@ async main. | |||
| [!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 immediately | |||
| 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. | |||
| >[!TIP] | |||
| 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 | |||
| will crash your application. Discord.Net will prevent exceptions in | |||
| event handlers from crashing your program, but any exceptions in your | |||
| 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 | |||
| Before we create and configure a Discord client, we will add a method | |||
| to handle Discord.Net's log events. | |||
| 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. | |||
| 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)] | |||
| [API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||
| ### Creating a Discord Client | |||
| 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 | |||
| 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 | |||
| 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 | |||
| similarly to other events in C#, so hook this event the way that | |||
| 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 | |||
| on your bot's application page on the [Discord Applications Portal]). | |||
|  | |||
| >[!IMPORTANT] | |||
| 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 | |||
| 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 | |||
| 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 | |||
| 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 following lines can now be added: | |||
| @@ -154,51 +159,55 @@ online in Discord. | |||
| >[!TIP] | |||
| Encountering a `PlatformNotSupportedException` when starting your bot? | |||
| 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. | |||
| [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 | |||
| [installing guide]: installing.md#installing-on-net-standard-11 | |||
| [installation guide]: installing.md#installing-on-net-standard-11 | |||
| ### 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 | |||
| begin handling messages that users are sending. | |||
| 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]. | |||
| 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 | |||
| 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 | |||
| to `!ping`. | |||
| 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 | |||
| channel object is of type [SocketMessageChannel], we can invoke the | |||
| `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: | |||
| [!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. | |||
| 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 | |||
| [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel | |||
| [completed program]: samples/intro/complete.cs | |||
| [Command Guide]: ../commands/commands.md | |||
| # Building a bot with commands | |||
| 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. | |||
| @@ -224,4 +234,4 @@ should be to separate the program (initialization and command handler), | |||
| the modules (handle commands), and the services (persistent storage, | |||
| pure functions, data manipulation). | |||
| **todo:** diagram of bot structure | |||
| **todo:** diagram of bot structure | |||
| @@ -1,7 +1,7 @@ | |||
| public async Task MainAsync() | |||
| { | |||
| // client.Log ... | |||
| client.MessageReceived += MessageReceived; | |||
| _client.MessageReceived += MessageReceived; | |||
| // ... | |||
| } | |||
| @@ -11,4 +11,4 @@ private async Task MessageReceived(SocketMessage message) | |||
| { | |||
| await message.Channel.SendMessageAsync("Pong!"); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| using System; | |||
| using System.Reflection; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| using Microsoft.Extensions.DependencyInjection; | |||
| using Discord; | |||
| @@ -8,12 +9,6 @@ using Discord.WebSocket; | |||
| 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 | |||
| static void Main(string[] args) | |||
| { | |||
| @@ -22,6 +17,13 @@ class Program | |||
| 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() | |||
| { | |||
| _client = new DiscordSocketClient(new DiscordSocketConfig | |||
| @@ -48,7 +50,6 @@ class Program | |||
| // that ask for a Func<LogMessage, Task>. | |||
| private static Task Logger(LogMessage message) | |||
| { | |||
| var cc = Console.ForegroundColor; | |||
| switch (message.Severity) | |||
| { | |||
| case LogSeverity.Critical: | |||
| @@ -66,8 +67,8 @@ class Program | |||
| Console.ForegroundColor = ConsoleColor.DarkGray; | |||
| 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, | |||
| // your project is targeting .NET 4.5.2 or lower. You'll need | |||
| @@ -80,7 +81,7 @@ class Program | |||
| private async Task MainAsync() | |||
| { | |||
| // Centralize the logic for commands into a seperate method. | |||
| // Centralize the logic for commands into a separate method. | |||
| await InitCommands(); | |||
| // Login and connect. | |||
| @@ -88,7 +89,7 @@ class Program | |||
| await _client.StartAsync(); | |||
| // Wait infinitely so your bot actually stays connected. | |||
| await Task.Delay(-1); | |||
| await Task.Delay(Timeout.Infinite); | |||
| } | |||
| private IServiceProvider _services; | |||
| @@ -105,10 +106,11 @@ class Program | |||
| _services = _map.BuildServiceProvider(); | |||
| // 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()); | |||
| // Or add Modules manually if you prefer to be a little more explicit: | |||
| 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. | |||
| _client.MessageReceived += HandleCommandAsync; | |||
| @@ -120,6 +122,11 @@ class Program | |||
| var msg = arg as SocketUserMessage; | |||
| 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 | |||
| int pos = 0; | |||
| // Replace the '!' with whatever character | |||
| @@ -132,7 +139,7 @@ class Program | |||
| var context = new SocketCommandContext(_client, msg); | |||
| // Execute the command. (result does not indicate a return value, | |||
| // rather an object stating if the command executed succesfully). | |||
| // rather an object stating if the command executed successfully). | |||
| var result = await _commands.ExecuteAsync(context, pos, _services); | |||
| // Uncomment the following lines if you want the bot | |||
| @@ -7,7 +7,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Discord.Net" Version="1.0.0-rc-00617" /> | |||
| <PackageReference Include="Discord.Net" Version="1.*" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -7,32 +7,34 @@ title: Terminology | |||
| ## 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 | |||
| 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. | |||
| `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 | |||
| Discord.Net, and if you are writing an implementation-agnostic library | |||
| or addon, you can rely on the core interfaces to ensure that your | |||
| addon will run on all platforms. | |||
| `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`). | |||
| @@ -1,11 +1,10 @@ | |||
| private Process CreateStream(string path) | |||
| { | |||
| var ffmpeg = new ProcessStartInfo | |||
| return Process.Start(new ProcessStartInfo | |||
| { | |||
| FileName = "ffmpeg", | |||
| Arguments = $"-i {path} -ac 2 -f s16le -ar 48000 pipe:1", | |||
| Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1", | |||
| UseShellExecute = false, | |||
| RedirectStandardOutput = true, | |||
| }; | |||
| return Process.Start(ffmpeg); | |||
| } | |||
| }); | |||
| } | |||
| @@ -1,9 +1,11 @@ | |||
| private async Task SendAsync(IAudioClient client, string path) | |||
| { | |||
| // Create FFmpeg using the previous example | |||
| var ffmpeg = CreateStream(path); | |||
| var output = ffmpeg.StandardOutput.BaseStream; | |||
| var discord = client.CreatePCMStream(AudioApplication.Mixed); | |||
| await output.CopyToAsync(discord); | |||
| await discord.FlushAsync(); | |||
| using (var ffmpeg = CreateStream(path)) | |||
| using (var output = ffmpeg.StandardOutput.BaseStream) | |||
| using (var discord = client.CreatePCMStream(AudioApplication.Mixed)) | |||
| { | |||
| try { await output.CopyToAsync(discord); } | |||
| finally { await discord.FlushAsync(); } | |||
| } | |||
| } | |||
| @@ -7,4 +7,4 @@ public async Task JoinChannel(IVoiceChannel channel = null) | |||
| // For the next step with transmitting audio, you would want to pass this Audio Client in to a service. | |||
| var audioClient = await channel.ConnectAsync(); | |||
| } | |||
| } | |||
| @@ -17,7 +17,7 @@ when developing on .NET Core, this is where you execute `dotnet run` | |||
| from; typically the same directory as your csproj). | |||
| For Windows Users, precompiled binaries are available for your | |||
| convienence [here](https://discord.foxbot.me/binaries/) | |||
| convienence [here](https://discord.foxbot.me/binaries/). | |||
| For Linux Users, you will need to compile [Sodium] and [Opus] from | |||
| 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. | |||
| 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)] | |||
| @@ -44,7 +44,7 @@ guild. To switch channels within a guild, invoke [ConnectAsync] on | |||
| another voice channel in the guild. | |||
| [IAudioClient]: xref:Discord.Audio.IAudioClient | |||
| [ConnectAsync]: xref:Discord.IVoiceChannel#Discord_IVoiceChannel_ConnectAsync | |||
| [ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__ | |||
| ## Transmitting Audio | |||
| @@ -84,7 +84,7 @@ Channels should be left at `2`, unless you specified a different value | |||
| for `-ac 2` when creating FFmpeg. | |||
| [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 | |||
| AudioOutStream. This step can be as complex as you'd like it to be, but | |||
| @@ -7,6 +7,6 @@ namespace Discord.Commands | |||
| [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] | |||
| public abstract class ParameterPreconditionAttribute : Attribute | |||
| { | |||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); | |||
| public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); | |||
| } | |||
| } | |||
| @@ -8,11 +8,11 @@ namespace Discord.Commands | |||
| { | |||
| /// <summary> | |||
| /// Specify a group that this precondition belongs to. Preconditions of the same group require only one | |||
| /// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see cref="null"/> | |||
| /// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see langword="null"/> | |||
| /// or not at all will require *all* preconditions to pass, just like normal (A && B). | |||
| /// </summary> | |||
| public string Group { get; set; } = null; | |||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services); | |||
| public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services); | |||
| } | |||
| } | |||
| @@ -41,7 +41,7 @@ namespace Discord.Commands | |||
| GuildPermission = null; | |||
| } | |||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| { | |||
| IGuildUser guildUser = null; | |||
| if (context.Guild != null) | |||
| @@ -57,13 +57,11 @@ namespace Discord.Commands | |||
| if (ChannelPermission.HasValue) | |||
| { | |||
| var guildChannel = context.Channel as IGuildChannel; | |||
| ChannelPermissions perms; | |||
| if (guildChannel != null) | |||
| if (context.Channel is IGuildChannel guildChannel) | |||
| perms = guildUser.GetPermissions(guildChannel); | |||
| else | |||
| perms = ChannelPermissions.All(guildChannel); | |||
| perms = ChannelPermissions.All(context.Channel); | |||
| if (!perms.Has(ChannelPermission.Value)) | |||
| return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); | |||
| @@ -38,7 +38,7 @@ namespace Discord.Commands | |||
| Contexts = contexts; | |||
| } | |||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| { | |||
| bool isValid = false; | |||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
| public class RequireNsfwAttribute : PreconditionAttribute | |||
| { | |||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| { | |||
| if (context.Channel is ITextChannel text && text.IsNsfw) | |||
| return Task.FromResult(PreconditionResult.FromSuccess()); | |||
| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| #pragma warning disable CS0618 | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| using Microsoft.Extensions.DependencyInjection; | |||
| @@ -11,7 +12,7 @@ namespace Discord.Commands | |||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||
| public class RequireOwnerAttribute : PreconditionAttribute | |||
| { | |||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| { | |||
| switch (context.Client.TokenType) | |||
| { | |||
| @@ -42,7 +42,7 @@ namespace Discord.Commands | |||
| GuildPermission = null; | |||
| } | |||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | |||
| { | |||
| var guildUser = context.User as IGuildUser; | |||
| @@ -56,13 +56,11 @@ namespace Discord.Commands | |||
| if (ChannelPermission.HasValue) | |||
| { | |||
| var guildChannel = context.Channel as IGuildChannel; | |||
| ChannelPermissions perms; | |||
| if (guildChannel != null) | |||
| if (context.Channel is IGuildChannel guildChannel) | |||
| perms = guildUser.GetPermissions(guildChannel); | |||
| else | |||
| perms = ChannelPermissions.All(guildChannel); | |||
| perms = ChannelPermissions.All(context.Channel); | |||
| if (!perms.Has(ChannelPermission.Value)) | |||
| return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); | |||
| @@ -14,7 +14,7 @@ namespace Discord.Commands | |||
| QuotedParameter | |||
| } | |||
| public static async Task<ParseResult> ParseArgs(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) | |||
| public static async Task<ParseResult> ParseArgsAsync(CommandInfo command, ICommandContext context, IServiceProvider services, string input, int startPos) | |||
| { | |||
| ParameterInfo curParam = null; | |||
| StringBuilder argBuilder = new StringBuilder(input.Length); | |||
| @@ -111,7 +111,7 @@ namespace Discord.Commands | |||
| if (curParam == null) | |||
| return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); | |||
| var typeReaderResult = await curParam.Parse(context, argString, services).ConfigureAwait(false); | |||
| var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false); | |||
| if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) | |||
| return ParseResult.FromError(typeReaderResult); | |||
| @@ -134,7 +134,7 @@ namespace Discord.Commands | |||
| if (curParam != null && curParam.IsRemainder) | |||
| { | |||
| var typeReaderResult = await curParam.Parse(context, argBuilder.ToString(), services).ConfigureAwait(false); | |||
| var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false); | |||
| if (!typeReaderResult.IsSuccess) | |||
| return ParseResult.FromError(typeReaderResult); | |||
| argList.Add(typeReaderResult); | |||
| @@ -2,17 +2,18 @@ | |||
| { | |||
| 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 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; | |||
| /// <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; | |||
| /// <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; | |||
| } | |||
| } | |||
| } | |||
| @@ -78,7 +78,7 @@ namespace Discord.Commands | |||
| { | |||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||
| { | |||
| var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); | |||
| var result = await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false); | |||
| if (!result.IsSuccess) | |||
| return result; | |||
| } | |||
| @@ -87,7 +87,7 @@ namespace Discord.Commands | |||
| { | |||
| var results = new List<PreconditionResult>(); | |||
| foreach (PreconditionAttribute precondition in preconditionGroup) | |||
| results.Add(await precondition.CheckPermissions(context, this, services).ConfigureAwait(false)); | |||
| results.Add(await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false)); | |||
| if (!results.Any(p => p.IsSuccess)) | |||
| return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); | |||
| @@ -117,7 +117,7 @@ namespace Discord.Commands | |||
| return ParseResult.FromError(preconditionResult); | |||
| string input = searchResult.Text.Substring(startIndex); | |||
| return await CommandParser.ParseArgs(this, context, services, input, 0).ConfigureAwait(false); | |||
| return await CommandParser.ParseArgsAsync(this, context, services, input, 0).ConfigureAwait(false); | |||
| } | |||
| public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) | |||
| @@ -163,11 +163,11 @@ namespace Discord.Commands | |||
| switch (RunMode) | |||
| { | |||
| case RunMode.Sync: //Always sync | |||
| return await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); | |||
| return await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); | |||
| case RunMode.Async: //Always async | |||
| var t2 = Task.Run(async () => | |||
| { | |||
| await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); | |||
| await ExecuteAsyncInternalAsync(context, args, services).ConfigureAwait(false); | |||
| }); | |||
| break; | |||
| } | |||
| @@ -179,7 +179,7 @@ namespace Discord.Commands | |||
| } | |||
| } | |||
| private async Task<IResult> ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) | |||
| private async Task<IResult> ExecuteAsyncInternalAsync(ICommandContext context, object[] args, IServiceProvider services) | |||
| { | |||
| await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | |||
| try | |||
| @@ -199,10 +199,13 @@ namespace Discord.Commands | |||
| return result; | |||
| } | |||
| else | |||
| { | |||
| await task.ConfigureAwait(false); | |||
| var result = ExecuteResult.FromSuccess(); | |||
| await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); | |||
| } | |||
| var executeResult = ExecuteResult.FromSuccess(); | |||
| await Module.Service._commandExecutedEvent.InvokeAsync(this, context, executeResult).ConfigureAwait(false); | |||
| return executeResult; | |||
| } | |||
| catch (Exception ex) | |||
| @@ -48,7 +48,7 @@ namespace Discord.Commands | |||
| foreach (var precondition in Preconditions) | |||
| { | |||
| var result = await precondition.CheckPermissions(context, this, arg, services).ConfigureAwait(false); | |||
| var result = await precondition.CheckPermissionsAsync(context, this, arg, services).ConfigureAwait(false); | |||
| if (!result.IsSuccess) | |||
| return result; | |||
| } | |||
| @@ -56,10 +56,10 @@ namespace Discord.Commands | |||
| return PreconditionResult.FromSuccess(); | |||
| } | |||
| public async Task<TypeReaderResult> Parse(ICommandContext context, string input, IServiceProvider services = null) | |||
| public async Task<TypeReaderResult> ParseAsync(ICommandContext context, string input, IServiceProvider services = null) | |||
| { | |||
| services = services ?? EmptyServiceProvider.Instance; | |||
| return await _reader.Read(context, input, services).ConfigureAwait(false); | |||
| return await _reader.ReadAsync(context, input, services).ConfigureAwait(false); | |||
| } | |||
| public override string ToString() => Name; | |||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||
| internal class ChannelTypeReader<T> : TypeReader | |||
| where T : class, IChannel | |||
| { | |||
| public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| if (context.Guild != null) | |||
| { | |||
| @@ -44,7 +44,7 @@ namespace Discord.Commands | |||
| _enumsByValue = byValueBuilder.ToImmutable(); | |||
| } | |||
| public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| object enumValue; | |||
| @@ -7,7 +7,7 @@ namespace Discord.Commands | |||
| internal class MessageTypeReader<T> : TypeReader | |||
| where T : class, IMessage | |||
| { | |||
| public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| ulong id; | |||
| @@ -24,11 +24,11 @@ namespace Discord.Commands | |||
| _baseTypeReader = baseTypeReader; | |||
| } | |||
| public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) | |||
| return TypeReaderResult.FromSuccess(new T?()); | |||
| return await _baseTypeReader.Read(context, input, services); ; | |||
| return await _baseTypeReader.ReadAsync(context, input, services); | |||
| } | |||
| } | |||
| } | |||
| @@ -30,7 +30,7 @@ namespace Discord.Commands | |||
| _score = score; | |||
| } | |||
| public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| if (_tryParse(input, out T value)) | |||
| return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); | |||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||
| internal class RoleTypeReader<T> : TypeReader | |||
| where T : class, IRole | |||
| { | |||
| public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| ulong id; | |||
| @@ -5,6 +5,6 @@ namespace Discord.Commands | |||
| { | |||
| public abstract class TypeReader | |||
| { | |||
| public abstract Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services); | |||
| public abstract Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services); | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ namespace Discord.Commands | |||
| internal class UserTypeReader<T> : TypeReader | |||
| where T : class, IUser | |||
| { | |||
| public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||
| public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | |||
| { | |||
| var results = new Dictionary<ulong, TypeReaderValue>(); | |||
| IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? | |||
| @@ -24,7 +24,7 @@ namespace Discord | |||
| /// <param name="maxAge"> The time (in seconds) until the invite expires. Set to null to never expire. </param> | |||
| /// <param name="maxUses"> The max amount of times this invite may be used. Set to null to have unlimited uses. </param> | |||
| /// <param name="isTemporary"> If true, a user accepting this invite will be kicked from the guild after closing their client. </param> | |||
| Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 1800, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); | |||
| Task<IInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); | |||
| /// <summary> Returns a collection of all invites to this channel. </summary> | |||
| Task<IReadOnlyCollection<IInviteMetadata>> GetInvitesAsync(RequestOptions options = null); | |||
| @@ -29,10 +29,6 @@ namespace Discord | |||
| CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
| /// <summary> Gets a collection of pinned messages in this channel. </summary> | |||
| 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> | |||
| Task TriggerTypingAsync(RequestOptions options = null); | |||
| @@ -1,4 +1,5 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| @@ -11,6 +12,11 @@ namespace Discord | |||
| /// <summary> Gets the current topic for this text channel. </summary> | |||
| 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> | |||
| Task ModifyAsync(Action<TextChannelProperties> func, RequestOptions options = null); | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| using System.Collections.Generic; | |||
| namespace Discord | |||
| { | |||
| public class EmoteProperties | |||
| { | |||
| public Optional<string> Name { get; set; } | |||
| public Optional<IEnumerable<IRole>> Roles { get; set; } | |||
| } | |||
| } | |||
| @@ -120,5 +120,14 @@ namespace Discord | |||
| Task DownloadUsersAsync(); | |||
| /// <summary> Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. </summary> | |||
| Task<int> PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); | |||
| /// <summary> Gets a specific emote from this guild. </summary> | |||
| Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null); | |||
| /// <summary> Creates a new emote in this guild. </summary> | |||
| Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null); | |||
| /// <summary> Modifies an existing emote in this guild. </summary> | |||
| Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null); | |||
| /// <summary> Deletes an existing emote from this guild. </summary> | |||
| Task DeleteEmoteAsync(GuildEmote emote, 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, | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ namespace Discord | |||
| /// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> | |||
| public static readonly ChannelPermissions None = new ChannelPermissions(); | |||
| /// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary> | |||
| public static readonly ChannelPermissions Text = new ChannelPermissions(0b00100_0000000_1111111110001_010001); | |||
| public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); | |||
| /// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary> | |||
| public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); | |||
| /// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary> | |||
| @@ -36,7 +36,7 @@ namespace Discord | |||
| /// <summary> If True, a user may create invites. </summary> | |||
| public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); | |||
| /// <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> | |||
| 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> | |||
| 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> | |||
| 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? 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? 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; | |||
| 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, readMessages, ChannelPermission.ReadMessages); | |||
| Permissions.SetValue(ref value, sendMessages, ChannelPermission.SendMessages); | |||
| @@ -107,7 +107,7 @@ namespace Discord | |||
| Permissions.SetValue(ref value, deafenMembers, ChannelPermission.DeafenMembers); | |||
| Permissions.SetValue(ref value, moveMembers, ChannelPermission.MoveMembers); | |||
| 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); | |||
| RawValue = value; | |||
| @@ -119,10 +119,10 @@ namespace Discord | |||
| 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 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, | |||
| 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> | |||
| @@ -131,21 +131,21 @@ namespace Discord | |||
| 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 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, | |||
| 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 List<ChannelPermission> ToList() | |||
| { | |||
| 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; | |||
| } | |||
| @@ -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> | |||
| public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | |||
| /// <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_1111111110011_111111); | |||
| /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | |||
| public ulong RawValue { get; } | |||
| @@ -28,9 +28,12 @@ namespace Discord | |||
| public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); | |||
| /// <summary> If True, a user may adjust guild properties. </summary> | |||
| public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); | |||
| /// <summary> If true, a user may add reactions. </summary> | |||
| 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> | |||
| public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); | |||
| /// <summary> If True, a user may send messages. </summary> | |||
| @@ -77,13 +80,13 @@ namespace Discord | |||
| /// <summary> Creates a new GuildPermissions with the provided packed value. </summary> | |||
| public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | |||
| 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? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = 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? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | |||
| private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, bool? kickMembers = 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? 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? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, | |||
| bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) | |||
| { | |||
| ulong value = initialValue; | |||
| @@ -92,9 +95,10 @@ namespace Discord | |||
| Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); | |||
| Permissions.SetValue(ref value, kickMembers, GuildPermission.KickMembers); | |||
| 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, addReactions, GuildPermission.AddReactions); | |||
| Permissions.SetValue(ref value, viewAuditLog, GuildPermission.ViewAuditLog); | |||
| Permissions.SetValue(ref value, readMessages, GuildPermission.ReadMessages); | |||
| Permissions.SetValue(ref value, sendMessages, GuildPermission.SendMessages); | |||
| Permissions.SetValue(ref value, sendTTSMessages, GuildPermission.SendTTSMessages); | |||
| @@ -103,7 +107,7 @@ namespace Discord | |||
| Permissions.SetValue(ref value, attachFiles, GuildPermission.AttachFiles); | |||
| Permissions.SetValue(ref value, readMessageHistory, GuildPermission.ReadMessageHistory); | |||
| 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, speak, GuildPermission.Speak); | |||
| Permissions.SetValue(ref value, muteMembers, GuildPermission.MuteMembers); | |||
| @@ -120,42 +124,50 @@ namespace Discord | |||
| } | |||
| /// <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 addReactions = false, | |||
| bool addReactions = false, bool viewAuditLog = 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 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) | |||
| : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, | |||
| readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | |||
| manageWebhooks, manageEmojis) { } | |||
| : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers, | |||
| administrator: administrator, manageChannels: manageChannels, manageGuild: manageGuild, addReactions: addReactions, | |||
| viewAuditLog: viewAuditLog, readMessages: readMessages, sendMessages: sendMessages, sendTTSMessages: sendTTSMessages, | |||
| manageMessages: manageMessages, embedLinks: embedLinks, attachFiles: attachFiles, readMessageHistory: readMessageHistory, | |||
| mentionEveryone: mentionEveryone, useExternalEmojis: useExternalEmojis, connect: connect, speak: speak, muteMembers: muteMembers, | |||
| deafenMembers: deafenMembers, moveMembers: moveMembers, useVoiceActivation: useVoiceActivation, changeNickname: changeNickname, | |||
| manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, manageEmojis: manageEmojis) | |||
| { } | |||
| /// <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? addReactions = null, | |||
| bool? addReactions = null, bool? viewAuditLog = 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? 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) | |||
| => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, | |||
| readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, | |||
| speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, | |||
| manageWebhooks, manageEmojis); | |||
| => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, | |||
| viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, | |||
| readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, | |||
| useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); | |||
| public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); | |||
| public List<GuildPermission> ToList() | |||
| { | |||
| 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; | |||
| } | |||
| @@ -23,7 +23,7 @@ namespace Discord | |||
| /// <summary> If Allowed, a user may create invites. </summary> | |||
| public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); | |||
| /// <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> | |||
| public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | |||
| /// <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> | |||
| 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> | |||
| 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? 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? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? managePermissions = null, | |||
| PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? manageRoles = null, | |||
| PermValue? manageWebhooks = null) | |||
| { | |||
| 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, readMessages, ChannelPermission.ReadMessages); | |||
| 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, moveMembers, ChannelPermission.MoveMembers); | |||
| 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); | |||
| 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 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 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, | |||
| 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> | |||
| 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? 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? 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, | |||
| embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, | |||
| moveMembers, useVoiceActivation, managePermissions, manageWebhooks); | |||
| moveMembers, useVoiceActivation, manageRoles, manageWebhooks); | |||
| public List<ChannelPermission> ToAllowList() | |||
| { | |||
| 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; | |||
| } | |||
| public List<ChannelPermission> ToDenyList() | |||
| { | |||
| 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; | |||
| } | |||
| @@ -1,7 +1,10 @@ | |||
| namespace Discord | |||
| using System; | |||
| namespace Discord | |||
| { | |||
| public enum TokenType | |||
| { | |||
| [Obsolete("User logins are being deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827")] | |||
| User, | |||
| Bearer, | |||
| Bot, | |||
| @@ -10,7 +10,7 @@ namespace Discord | |||
| public static Optional<T> Unspecified => default(Optional<T>); | |||
| private readonly T _value; | |||
| /// <summary> Gets the value for this paramter. </summary> | |||
| /// <summary> Gets the value for this parameter. </summary> | |||
| public T Value | |||
| { | |||
| get | |||
| @@ -7,84 +7,84 @@ namespace Discord | |||
| public const int MaxBits = 53; | |||
| [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)] | |||
| 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)] | |||
| 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; | |||
| else if (HasBit(deny, bit)) | |||
| else if (HasFlag(deny, flag)) | |||
| return PermValue.Deny; | |||
| else | |||
| return PermValue.Inherit; | |||
| } | |||
| [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)] | |||
| 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)] | |||
| 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)] | |||
| 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)] | |||
| 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)] | |||
| 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 == true) | |||
| SetBit(ref rawValue, bit); | |||
| SetFlag(ref rawValue, flag); | |||
| else | |||
| UnsetBit(ref rawValue, bit); | |||
| UnsetFlag(ref rawValue, flag); | |||
| } | |||
| } | |||
| [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)] | |||
| 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)] | |||
| 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) | |||
| { | |||
| switch (value) | |||
| { | |||
| case PermValue.Allow: | |||
| SetBit(ref allow, bit); | |||
| UnsetBit(ref deny, bit); | |||
| SetFlag(ref allow, flag); | |||
| UnsetFlag(ref deny, flag); | |||
| break; | |||
| case PermValue.Deny: | |||
| UnsetBit(ref allow, bit); | |||
| SetBit(ref deny, bit); | |||
| UnsetFlag(ref allow, flag); | |||
| SetFlag(ref deny, flag); | |||
| break; | |||
| default: | |||
| UnsetBit(ref allow, bit); | |||
| UnsetBit(ref deny, bit); | |||
| UnsetFlag(ref allow, flag); | |||
| UnsetFlag(ref deny, flag); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| [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)] | |||
| 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)] | |||
| 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) | |||
| => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); | |||
| @@ -188,7 +188,8 @@ namespace Discord | |||
| var minimum = SnowflakeUtils.ToSnowflake(DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(14))); | |||
| for (var i = 0; i < collection.Length; i++) | |||
| { | |||
| if (collection[i] <= minimum) | |||
| if (collection[i] == 0) continue; | |||
| if (collection[i] <= minimum) | |||
| throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| #pragma warning disable CS1591 | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Rest | |||
| { | |||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
| internal class CreateGuildEmoteParams | |||
| { | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("image")] | |||
| public Image Image { get; set; } | |||
| [JsonProperty("roles")] | |||
| public Optional<ulong[]> RoleIds { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| #pragma warning disable CS1591 | |||
| using Newtonsoft.Json; | |||
| namespace Discord.API.Rest | |||
| { | |||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
| internal class ModifyGuildEmoteParams | |||
| { | |||
| [JsonProperty("name")] | |||
| public Optional<string> Name { get; set; } | |||
| [JsonProperty("roles")] | |||
| public Optional<ulong[]> RoleIds { get; set; } | |||
| } | |||
| } | |||
| @@ -85,7 +85,8 @@ namespace Discord.Rest | |||
| await _loggedInEvent.InvokeAsync().ConfigureAwait(false); | |||
| } | |||
| internal virtual Task OnLoginAsync(TokenType tokenType, string token) { return Task.Delay(0); } | |||
| internal virtual Task OnLoginAsync(TokenType tokenType, string token) | |||
| => Task.Delay(0); | |||
| /// <inheritdoc /> | |||
| public async Task LogoutAsync() | |||
| @@ -110,7 +111,8 @@ namespace Discord.Rest | |||
| await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); | |||
| } | |||
| internal virtual Task OnLogoutAsync() { return Task.Delay(0); } | |||
| internal virtual Task OnLogoutAsync() | |||
| => Task.Delay(0); | |||
| internal virtual void Dispose(bool disposing) | |||
| { | |||
| @@ -127,7 +129,8 @@ namespace Discord.Rest | |||
| ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; | |||
| ISelfUser IDiscordClient.CurrentUser => CurrentUser; | |||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) { throw new NotSupportedException(); } | |||
| Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
| => throw new NotSupportedException(); | |||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IChannel>(null); | |||
| @@ -148,7 +151,8 @@ namespace Discord.Rest | |||
| => Task.FromResult<IGuild>(null); | |||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(ImmutableArray.Create<IGuild>()); | |||
| Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) { throw new NotSupportedException(); } | |||
| Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
| => throw new NotSupportedException(); | |||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma warning disable CS1591 | |||
| #pragma warning disable CS0618 | |||
| using Discord.API.Rest; | |||
| using Discord.Net; | |||
| using Discord.Net.Converters; | |||
| @@ -1065,6 +1066,50 @@ namespace Discord.API | |||
| return await SendJsonAsync<IReadOnlyCollection<Role>>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); | |||
| } | |||
| //Guild emoji | |||
| public async Task<Emoji> GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
| Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(guildId: guildId); | |||
| return await SendAsync<Emoji>("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); | |||
| } | |||
| public async Task<Emoji> CreateGuildEmoteAsync(ulong guildId, Rest.CreateGuildEmoteParams args, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
| Preconditions.NotNull(args, nameof(args)); | |||
| Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | |||
| Preconditions.NotNull(args.Image.Stream, nameof(args.Image)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(guildId: guildId); | |||
| return await SendJsonAsync<Emoji>("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options); | |||
| } | |||
| public async Task<Emoji> ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
| Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); | |||
| Preconditions.NotNull(args, nameof(args)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(guildId: guildId); | |||
| return await SendJsonAsync<Emoji>("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options); | |||
| } | |||
| public async Task DeleteGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
| Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); | |||
| options = RequestOptions.CreateOrClone(options); | |||
| var ids = new BucketIds(guildId: guildId); | |||
| await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); | |||
| } | |||
| //Users | |||
| public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) | |||
| { | |||
| @@ -184,7 +184,7 @@ namespace Discord.Rest | |||
| 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) | |||
| { | |||
| var msgs = messageIds.ToArray(); | |||
| @@ -48,8 +48,8 @@ namespace Discord.Rest | |||
| string IChannel.Name => null; | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); //Overriden | |||
| => Task.FromResult<IUser>(null); //Overridden | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overriden | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden | |||
| } | |||
| } | |||
| @@ -72,11 +72,6 @@ namespace Discord.Rest | |||
| 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); | |||
| 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| 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) | |||
| => 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| @@ -123,7 +123,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||
| => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); | |||
| public override string ToString() => Name; | |||
| @@ -158,14 +158,14 @@ namespace Discord.Rest | |||
| => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); | |||
| IAsyncEnumerable<IReadOnlyCollection<IGuildUser>> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overriden //Overriden in Text/Voice | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IGuildUser>>(); //Overridden //Overridden in Text/Voice | |||
| Task<IGuildUser> IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuildUser>(null); //Overriden in Text/Voice | |||
| => Task.FromResult<IGuildUser>(null); //Overridden in Text/Voice | |||
| //IChannel | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overriden in Text/Voice | |||
| => AsyncEnumerable.Empty<IReadOnlyCollection<IUser>>(); //Overridden in Text/Voice | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(null); //Overriden in Text/Voice | |||
| => Task.FromResult<IUser>(null); //Overridden in Text/Voice | |||
| } | |||
| } | |||
| @@ -42,11 +42,6 @@ namespace Discord.Rest | |||
| public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null) | |||
| => 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| @@ -262,5 +262,46 @@ namespace Discord.Rest | |||
| model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false); | |||
| return model.Pruned; | |||
| } | |||
| //Emotes | |||
| public static async Task<GuildEmote> GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) | |||
| { | |||
| var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options); | |||
| return emote.ToEntity(); | |||
| } | |||
| public static async Task<GuildEmote> CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional<IEnumerable<IRole>> roles, | |||
| RequestOptions options) | |||
| { | |||
| var apiargs = new CreateGuildEmoteParams | |||
| { | |||
| Name = name, | |||
| Image = image.ToModel() | |||
| }; | |||
| if (roles.IsSpecified) | |||
| apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray(); | |||
| var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options); | |||
| return emote.ToEntity(); | |||
| } | |||
| public static async Task<GuildEmote> ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action<EmoteProperties> func, | |||
| RequestOptions options) | |||
| { | |||
| if (func == null) throw new ArgumentNullException(nameof(func)); | |||
| var props = new EmoteProperties(); | |||
| func(props); | |||
| var apiargs = new ModifyGuildEmoteParams | |||
| { | |||
| Name = props.Name | |||
| }; | |||
| if (props.Roles.IsSpecified) | |||
| apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray(); | |||
| var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options); | |||
| return emote.ToEntity(); | |||
| } | |||
| public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) | |||
| => client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options); | |||
| } | |||
| } | |||
| @@ -267,6 +267,16 @@ namespace Discord.Rest | |||
| public override string ToString() => Name; | |||
| private string DebuggerDisplay => $"{Name} ({Id})"; | |||
| //Emotes | |||
| public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | |||
| => GuildHelper.GetEmoteAsync(this, Discord, id, options); | |||
| public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null) | |||
| => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); | |||
| public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null) | |||
| => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); | |||
| public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) | |||
| => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); | |||
| //IGuild | |||
| bool IGuild.Available => Available; | |||
| IAudioClient IGuild.AudioClient => null; | |||
| @@ -1,3 +1,5 @@ | |||
| using System; | |||
| namespace Discord | |||
| { | |||
| public static class EmbedBuilderExtensions | |||
| @@ -19,5 +21,38 @@ namespace Discord | |||
| public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IGuildUser user) => | |||
| builder.WithAuthor($"{user.Nickname ?? user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | |||
| public static EmbedBuilder ToEmbedBuilder(this IEmbed embed) | |||
| { | |||
| if (embed.Type != EmbedType.Rich) | |||
| throw new InvalidOperationException($"Only {nameof(EmbedType.Rich)} embeds may be built."); | |||
| var builder = new EmbedBuilder | |||
| { | |||
| Author = new EmbedAuthorBuilder | |||
| { | |||
| Name = embed.Author?.Name, | |||
| IconUrl = embed.Author?.IconUrl, | |||
| Url = embed.Author?.Url | |||
| }, | |||
| Color = embed.Color ?? Color.Default, | |||
| Description = embed.Description, | |||
| Footer = new EmbedFooterBuilder | |||
| { | |||
| Text = embed.Footer?.Text, | |||
| IconUrl = embed.Footer?.IconUrl | |||
| }, | |||
| ImageUrl = embed.Image?.Url, | |||
| ThumbnailUrl = embed.Thumbnail?.Url, | |||
| Timestamp = embed.Timestamp, | |||
| Title = embed.Title, | |||
| Url = embed.Url | |||
| }; | |||
| foreach (var field in embed.Fields) | |||
| builder.AddField(field.Name, field.Value, field.Inline); | |||
| return builder; | |||
| } | |||
| } | |||
| } | |||
| @@ -22,7 +22,7 @@ namespace Discord.Net.Rest | |||
| private CancellationToken _cancelToken; | |||
| private bool _isDisposed; | |||
| public DefaultRestClient(string baseUrl) | |||
| public DefaultRestClient(string baseUrl, bool useProxy = false) | |||
| { | |||
| _baseUrl = baseUrl; | |||
| @@ -30,7 +30,7 @@ namespace Discord.Net.Rest | |||
| { | |||
| AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, | |||
| UseCookies = false, | |||
| UseProxy = false | |||
| UseProxy = useProxy, | |||
| }); | |||
| SetHeader("accept-encoding", "gzip, deflate"); | |||
| @@ -4,16 +4,21 @@ namespace Discord.Net.Rest | |||
| { | |||
| public static class DefaultRestClientProvider | |||
| { | |||
| public static readonly RestClientProvider Instance = url => | |||
| public static readonly RestClientProvider Instance = Create(); | |||
| public static RestClientProvider Create(bool useProxy = false) | |||
| { | |||
| try | |||
| { | |||
| return new DefaultRestClient(url); | |||
| } | |||
| catch (PlatformNotSupportedException ex) | |||
| return url => | |||
| { | |||
| throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex); | |||
| } | |||
| }; | |||
| try | |||
| { | |||
| return new DefaultRestClient(url, useProxy); | |||
| } | |||
| catch (PlatformNotSupportedException ex) | |||
| { | |||
| throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex); | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| } | |||
| @@ -53,11 +53,6 @@ namespace Discord.Rpc | |||
| 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); | |||
| 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| 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) | |||
| => 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| @@ -52,7 +52,7 @@ namespace Discord.Rpc | |||
| public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||
| => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); | |||
| public override string ToString() => Name; | |||
| @@ -13,8 +13,6 @@ namespace Discord.API.Gateway | |||
| public IDictionary<string, string> Properties { get; set; } | |||
| [JsonProperty("large_threshold")] | |||
| public int LargeThreshold { get; set; } | |||
| [JsonProperty("compress")] | |||
| public bool UseCompression { get; set; } | |||
| [JsonProperty("shard")] | |||
| public Optional<int[]> ShardingParams { get; set; } | |||
| } | |||
| @@ -0,0 +1,193 @@ | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public partial class BaseSocketClient | |||
| { | |||
| //Channels | |||
| /// <summary> Fired when a channel is created. </summary> | |||
| public event Func<SocketChannel, Task> ChannelCreated | |||
| { | |||
| add { _channelCreatedEvent.Add(value); } | |||
| remove { _channelCreatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| /// <summary> Fired when a channel is destroyed. </summary> | |||
| public event Func<SocketChannel, Task> ChannelDestroyed { | |||
| add { _channelDestroyedEvent.Add(value); } | |||
| remove { _channelDestroyedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| /// <summary> Fired when a channel is updated. </summary> | |||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated { | |||
| add { _channelUpdatedEvent.Add(value); } | |||
| remove { _channelUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | |||
| //Messages | |||
| /// <summary> Fired when a message is received. </summary> | |||
| public event Func<SocketMessage, Task> MessageReceived { | |||
| add { _messageReceivedEvent.Add(value); } | |||
| remove { _messageReceivedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>(); | |||
| /// <summary> Fired when a message is deleted. </summary> | |||
| public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | |||
| add { _messageDeletedEvent.Add(value); } | |||
| remove { _messageDeletedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| /// <summary> Fired when a message is updated. </summary> | |||
| public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { | |||
| add { _messageUpdatedEvent.Add(value); } | |||
| remove { _messageUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>(); | |||
| /// <summary> Fired when a reaction is added to a message. </summary> | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | |||
| add { _reactionAddedEvent.Add(value); } | |||
| remove { _reactionAddedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| /// <summary> Fired when a reaction is removed from a message. </summary> | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { | |||
| add { _reactionRemovedEvent.Add(value); } | |||
| remove { _reactionRemovedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| /// <summary> Fired when all reactions to a message are cleared. </summary> | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared { | |||
| add { _reactionsClearedEvent.Add(value); } | |||
| remove { _reactionsClearedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| //Roles | |||
| /// <summary> Fired when a role is created. </summary> | |||
| public event Func<SocketRole, Task> RoleCreated { | |||
| add { _roleCreatedEvent.Add(value); } | |||
| remove { _roleCreatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| /// <summary> Fired when a role is deleted. </summary> | |||
| public event Func<SocketRole, Task> RoleDeleted { | |||
| add { _roleDeletedEvent.Add(value); } | |||
| remove { _roleDeletedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| /// <summary> Fired when a role is updated. </summary> | |||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated { | |||
| add { _roleUpdatedEvent.Add(value); } | |||
| remove { _roleUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>(); | |||
| //Guilds | |||
| /// <summary> Fired when the connected account joins a guild. </summary> | |||
| public event Func<SocketGuild, Task> JoinedGuild { | |||
| add { _joinedGuildEvent.Add(value); } | |||
| remove { _joinedGuildEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| /// <summary> Fired when the connected account leaves a guild. </summary> | |||
| public event Func<SocketGuild, Task> LeftGuild { | |||
| add { _leftGuildEvent.Add(value); } | |||
| remove { _leftGuildEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| /// <summary> Fired when a guild becomes available. </summary> | |||
| public event Func<SocketGuild, Task> GuildAvailable { | |||
| add { _guildAvailableEvent.Add(value); } | |||
| remove { _guildAvailableEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| /// <summary> Fired when a guild becomes unavailable. </summary> | |||
| public event Func<SocketGuild, Task> GuildUnavailable { | |||
| add { _guildUnavailableEvent.Add(value); } | |||
| remove { _guildUnavailableEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| /// <summary> Fired when offline guild members are downloaded. </summary> | |||
| public event Func<SocketGuild, Task> GuildMembersDownloaded { | |||
| add { _guildMembersDownloadedEvent.Add(value); } | |||
| remove { _guildMembersDownloadedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| /// <summary> Fired when a guild is updated. </summary> | |||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated { | |||
| add { _guildUpdatedEvent.Add(value); } | |||
| remove { _guildUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | |||
| //Users | |||
| /// <summary> Fired when a user joins a guild. </summary> | |||
| public event Func<SocketGuildUser, Task> UserJoined { | |||
| add { _userJoinedEvent.Add(value); } | |||
| remove { _userJoinedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| /// <summary> Fired when a user leaves a guild. </summary> | |||
| public event Func<SocketGuildUser, Task> UserLeft { | |||
| add { _userLeftEvent.Add(value); } | |||
| remove { _userLeftEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| /// <summary> Fired when a user is banned from a guild. </summary> | |||
| public event Func<SocketUser, SocketGuild, Task> UserBanned { | |||
| add { _userBannedEvent.Add(value); } | |||
| remove { _userBannedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| /// <summary> Fired when a user is unbanned from a guild. </summary> | |||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned { | |||
| add { _userUnbannedEvent.Add(value); } | |||
| remove { _userUnbannedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| /// <summary> Fired when a user is updated. </summary> | |||
| public event Func<SocketUser, SocketUser, Task> UserUpdated { | |||
| add { _userUpdatedEvent.Add(value); } | |||
| remove { _userUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | |||
| /// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | |||
| public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated { | |||
| add { _guildMemberUpdatedEvent.Add(value); } | |||
| remove { _guildMemberUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||
| /// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | |||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated { | |||
| add { _userVoiceStateUpdatedEvent.Add(value); } | |||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>(); | |||
| /// <summary> Fired when the connected account is updated. </summary> | |||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated { | |||
| add { _selfUpdatedEvent.Add(value); } | |||
| remove { _selfUpdatedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | |||
| /// <summary> Fired when a user starts typing. </summary> | |||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping { | |||
| add { _userIsTypingEvent.Add(value); } | |||
| remove { _userIsTypingEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||
| /// <summary> Fired when a user joins a group channel. </summary> | |||
| public event Func<SocketGroupUser, Task> RecipientAdded { | |||
| add { _recipientAddedEvent.Add(value); } | |||
| remove { _recipientAddedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| /// <summary> Fired when a user is removed from a group channel. </summary> | |||
| public event Func<SocketGroupUser, Task> RecipientRemoved { | |||
| add { _recipientRemovedEvent.Add(value); } | |||
| remove { _recipientRemovedEvent.Remove(value); } | |||
| } | |||
| internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Threading.Tasks; | |||
| using Discord.API; | |||
| using Discord.Rest; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public abstract partial class BaseSocketClient : BaseDiscordClient, IDiscordClient | |||
| { | |||
| protected readonly DiscordSocketConfig _baseconfig; | |||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
| public abstract int Latency { get; protected set; } | |||
| public abstract UserStatus Status { get; protected set; } | |||
| public abstract Game? Game { get; protected set; } | |||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
| public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||
| public abstract IReadOnlyCollection<SocketGuild> Guilds { get; } | |||
| public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } | |||
| public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } | |||
| internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | |||
| : base(config, client) => _baseconfig = config; | |||
| private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
| => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||
| /// <inheritdoc /> | |||
| public abstract Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null); | |||
| /// <inheritdoc /> | |||
| public abstract SocketUser GetUser(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract SocketUser GetUser(string username, string discriminator); | |||
| /// <inheritdoc /> | |||
| public abstract SocketChannel GetChannel(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract SocketGuild GetGuild(ulong id); | |||
| /// <inheritdoc /> | |||
| public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
| /// <inheritdoc /> | |||
| public abstract Task StartAsync(); | |||
| /// <inheritdoc /> | |||
| public abstract Task StopAsync(); | |||
| public abstract Task SetStatusAsync(UserStatus status); | |||
| public abstract Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming); | |||
| public abstract Task DownloadUsersAsync(IEnumerable<IGuild> guilds); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) | |||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | |||
| => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); | |||
| /// <inheritdoc /> | |||
| public Task<RestInvite> GetInviteAsync(string inviteId, RequestOptions options = null) | |||
| => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); | |||
| // IDiscordClient | |||
| async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
| => await GetApplicationInfoAsync(options).ConfigureAwait(false); | |||
| Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IChannel>(GetChannel(id)); | |||
| Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||
| async Task<IReadOnlyCollection<IConnection>> IDiscordClient.GetConnectionsAsync(RequestOptions options) | |||
| => await GetConnectionsAsync(options).ConfigureAwait(false); | |||
| async Task<IInvite> IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) | |||
| => await GetInviteAsync(inviteId, options).ConfigureAwait(false); | |||
| Task<IGuild> IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IGuild>(GetGuild(id)); | |||
| Task<IReadOnlyCollection<IGuild>> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IGuild>>(Guilds); | |||
| async Task<IGuild> IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) | |||
| => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); | |||
| Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); | |||
| Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
| Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
| => Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
| Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
| => Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
| } | |||
| } | |||
| @@ -3,197 +3,36 @@ using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| //TODO: Add event docstrings | |||
| public partial class DiscordShardedClient | |||
| { | |||
| //Channels | |||
| public event Func<SocketChannel, Task> ChannelCreated | |||
| //General | |||
| /// <summary> Fired when a shard is connected to the Discord gateway. </summary> | |||
| public event Func<DiscordSocketClient, Task> ShardConnected | |||
| { | |||
| add { _channelCreatedEvent.Add(value); } | |||
| remove { _channelCreatedEvent.Remove(value); } | |||
| add { _shardConnectedEvent.Add(value); } | |||
| remove { _shardConnectedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| public event Func<SocketChannel, Task> ChannelDestroyed | |||
| private readonly AsyncEvent<Func<DiscordSocketClient, Task>> _shardConnectedEvent = new AsyncEvent<Func<DiscordSocketClient, Task>>(); | |||
| /// <summary> Fired when a shard is disconnected from the Discord gateway. </summary> | |||
| public event Func<Exception, DiscordSocketClient, Task> ShardDisconnected | |||
| { | |||
| add { _channelDestroyedEvent.Add(value); } | |||
| remove { _channelDestroyedEvent.Remove(value); } | |||
| add { _shardDisconnectedEvent.Add(value); } | |||
| remove { _shardDisconnectedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated | |||
| private readonly AsyncEvent<Func<Exception, DiscordSocketClient, Task>> _shardDisconnectedEvent = new AsyncEvent<Func<Exception, DiscordSocketClient, Task>>(); | |||
| /// <summary> Fired when a guild data for a shard has finished downloading. </summary> | |||
| public event Func<DiscordSocketClient, Task> ShardReady | |||
| { | |||
| add { _channelUpdatedEvent.Add(value); } | |||
| remove { _channelUpdatedEvent.Remove(value); } | |||
| add { _shardReadyEvent.Add(value); } | |||
| remove { _shardReadyEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | |||
| //Messages | |||
| public event Func<SocketMessage, Task> MessageReceived | |||
| { | |||
| add { _messageReceivedEvent.Add(value); } | |||
| remove { _messageReceivedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>(); | |||
| public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted | |||
| { | |||
| add { _messageDeletedEvent.Add(value); } | |||
| remove { _messageDeletedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated | |||
| { | |||
| add { _messageUpdatedEvent.Add(value); } | |||
| remove { _messageUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded | |||
| { | |||
| add { _reactionAddedEvent.Add(value); } | |||
| remove { _reactionAddedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved | |||
| { | |||
| add { _reactionRemovedEvent.Add(value); } | |||
| remove { _reactionRemovedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared | |||
| { | |||
| add { _reactionsClearedEvent.Add(value); } | |||
| remove { _reactionsClearedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| //Roles | |||
| public event Func<SocketRole, Task> RoleCreated | |||
| { | |||
| add { _roleCreatedEvent.Add(value); } | |||
| remove { _roleCreatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| public event Func<SocketRole, Task> RoleDeleted | |||
| { | |||
| add { _roleDeletedEvent.Add(value); } | |||
| remove { _roleDeletedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated | |||
| { | |||
| add { _roleUpdatedEvent.Add(value); } | |||
| remove { _roleUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>(); | |||
| //Guilds | |||
| public event Func<SocketGuild, Task> JoinedGuild | |||
| { | |||
| add { _joinedGuildEvent.Add(value); } | |||
| remove { _joinedGuildEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> LeftGuild | |||
| { | |||
| add { _leftGuildEvent.Add(value); } | |||
| remove { _leftGuildEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildAvailable | |||
| { | |||
| add { _guildAvailableEvent.Add(value); } | |||
| remove { _guildAvailableEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildUnavailable | |||
| { | |||
| add { _guildUnavailableEvent.Add(value); } | |||
| remove { _guildUnavailableEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildMembersDownloaded | |||
| { | |||
| add { _guildMembersDownloadedEvent.Add(value); } | |||
| remove { _guildMembersDownloadedEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated | |||
| { | |||
| add { _guildUpdatedEvent.Add(value); } | |||
| remove { _guildUpdatedEvent.Remove(value); } | |||
| } | |||
| private AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | |||
| //Users | |||
| public event Func<SocketGuildUser, Task> UserJoined | |||
| { | |||
| add { _userJoinedEvent.Add(value); } | |||
| remove { _userJoinedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| public event Func<SocketGuildUser, Task> UserLeft | |||
| { | |||
| add { _userLeftEvent.Add(value); } | |||
| remove { _userLeftEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| public event Func<SocketUser, SocketGuild, Task> UserBanned | |||
| { | |||
| add { _userBannedEvent.Add(value); } | |||
| remove { _userBannedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned | |||
| { | |||
| add { _userUnbannedEvent.Add(value); } | |||
| remove { _userUnbannedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| public event Func<SocketUser, SocketUser, Task> UserUpdated | |||
| { | |||
| add { _userUpdatedEvent.Add(value); } | |||
| remove { _userUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | |||
| public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated | |||
| { | |||
| add { _guildMemberUpdatedEvent.Add(value); } | |||
| remove { _guildMemberUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||
| public event Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task> UserPresenceUpdated | |||
| { | |||
| add { _userPresenceUpdatedEvent.Add(value); } | |||
| remove { _userPresenceUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task>> _userPresenceUpdatedEvent = new AsyncEvent<Func<Optional<SocketGuild>, SocketUser, SocketPresence, SocketPresence, Task>>(); | |||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated | |||
| { | |||
| add { _userVoiceStateUpdatedEvent.Add(value); } | |||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>(); | |||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated | |||
| { | |||
| add { _selfUpdatedEvent.Add(value); } | |||
| remove { _selfUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | |||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping | |||
| { | |||
| add { _userIsTypingEvent.Add(value); } | |||
| remove { _userIsTypingEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||
| public event Func<SocketGroupUser, Task> RecipientAdded | |||
| { | |||
| add { _recipientAddedEvent.Add(value); } | |||
| remove { _recipientAddedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| public event Func<SocketGroupUser, Task> RecipientRemoved | |||
| private readonly AsyncEvent<Func<DiscordSocketClient, Task>> _shardReadyEvent = new AsyncEvent<Func<DiscordSocketClient, Task>>(); | |||
| /// <summary> Fired when a shard receives a heartbeat from the Discord gateway. </summary> | |||
| public event Func<int, int, DiscordSocketClient, Task> ShardLatencyUpdated | |||
| { | |||
| add { _recipientRemovedEvent.Add(value); } | |||
| remove { _recipientRemovedEvent.Remove(value); } | |||
| add { _shardLatencyUpdatedEvent.Add(value); } | |||
| remove { _shardLatencyUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| private readonly AsyncEvent<Func<int, int, DiscordSocketClient, Task>> _shardLatencyUpdatedEvent = new AsyncEvent<Func<int, int, DiscordSocketClient, Task>>(); | |||
| } | |||
| } | |||
| } | |||
| @@ -9,7 +9,7 @@ using System.Threading; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public partial class DiscordShardedClient : BaseDiscordClient, IDiscordClient | |||
| public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient | |||
| { | |||
| private readonly DiscordSocketConfig _baseConfig; | |||
| private readonly SemaphoreSlim _connectionGroupLock; | |||
| @@ -20,16 +20,15 @@ namespace Discord.WebSocket | |||
| private bool _automaticShards; | |||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
| public int Latency => GetLatency(); | |||
| public UserStatus Status => _shards[0].Status; | |||
| public Game? Game => _shards[0].Game; | |||
| public override int Latency { get => GetLatency(); protected set { } } | |||
| public override UserStatus Status { get => _shards[0].Status; protected set { } } | |||
| public override Game? Game { get => _shards[0].Game; protected set { } } | |||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
| public new SocketSelfUser CurrentUser { get { return base.CurrentUser as SocketSelfUser; } private set { base.CurrentUser = value; } } | |||
| public IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | |||
| public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | |||
| public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | |||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | |||
| public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
| public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
| public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | |||
| @@ -115,15 +114,11 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| public async Task StartAsync() | |||
| { | |||
| await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); | |||
| } | |||
| public override async Task StartAsync() | |||
| => await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| public async Task StopAsync() | |||
| { | |||
| await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); | |||
| } | |||
| public override async Task StopAsync() | |||
| => await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); | |||
| public DiscordSocketClient GetShard(int id) | |||
| { | |||
| @@ -141,17 +136,15 @@ namespace Discord.WebSocket | |||
| => GetShardFor(guild.Id); | |||
| /// <inheritdoc /> | |||
| public async Task<RestApplication> GetApplicationInfoAsync() | |||
| => await _shards[0].GetApplicationInfoAsync().ConfigureAwait(false); | |||
| public override async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | |||
| => await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false); | |||
| /// <inheritdoc /> | |||
| public SocketGuild GetGuild(ulong id) => GetShardFor(id).GetGuild(id); | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) | |||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions()); | |||
| public override SocketGuild GetGuild(ulong id) | |||
| => GetShardFor(id).GetGuild(id); | |||
| /// <inheritdoc /> | |||
| public SocketChannel GetChannel(ulong id) | |||
| public override SocketChannel GetChannel(ulong id) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| { | |||
| @@ -175,11 +168,7 @@ namespace Discord.WebSocket | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| result += _shards[i].PrivateChannels.Count; | |||
| return result; | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync() | |||
| => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); | |||
| } | |||
| private IEnumerable<SocketGuild> GetGuilds() | |||
| { | |||
| @@ -195,14 +184,10 @@ namespace Discord.WebSocket | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| result += _shards[i].Guilds.Count; | |||
| return result; | |||
| } | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task<RestInvite> GetInviteAsync(string inviteId) | |||
| => ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); | |||
| /// <inheritdoc /> | |||
| public SocketUser GetUser(ulong id) | |||
| public override SocketUser GetUser(ulong id) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| { | |||
| @@ -213,7 +198,7 @@ namespace Discord.WebSocket | |||
| return null; | |||
| } | |||
| /// <inheritdoc /> | |||
| public SocketUser GetUser(string username, string discriminator) | |||
| public override SocketUser GetUser(string username, string discriminator) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| { | |||
| @@ -225,11 +210,11 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| public RestVoiceRegion GetVoiceRegion(string id) | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| => _shards[0].GetVoiceRegion(id); | |||
| /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | |||
| public async Task DownloadUsersAsync(IEnumerable<SocketGuild> guilds) | |||
| public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| { | |||
| @@ -248,12 +233,12 @@ namespace Discord.WebSocket | |||
| return (int)Math.Round(total / (double)_shards.Length); | |||
| } | |||
| public async Task SetStatusAsync(UserStatus status) | |||
| public override async Task SetStatusAsync(UserStatus status) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| await _shards[i].SetStatusAsync(status).ConfigureAwait(false); | |||
| } | |||
| public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) | |||
| public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) | |||
| { | |||
| for (int i = 0; i < _shards.Length; i++) | |||
| await _shards[i].SetGameAsync(name, streamUrl, streamType).ConfigureAwait(false); | |||
| @@ -281,6 +266,11 @@ namespace Discord.WebSocket | |||
| }; | |||
| } | |||
| client.Connected += () => _shardConnectedEvent.InvokeAsync(client); | |||
| client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); | |||
| client.Ready += () => _shardReadyEvent.InvokeAsync(client); | |||
| client.LatencyUpdated += (oldLatency, newLatency) => _shardLatencyUpdatedEvent.InvokeAsync(oldLatency, newLatency, client); | |||
| client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel); | |||
| client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel); | |||
| client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); | |||
| @@ -29,7 +29,11 @@ namespace Discord.API | |||
| private CancellationTokenSource _connectCancelToken; | |||
| private string _gatewayUrl; | |||
| private bool _isExplicitUrl; | |||
| //Store our decompression streams for zlib shared state | |||
| private MemoryStream _compressed; | |||
| private DeflateStream _decompressor; | |||
| internal IWebSocketClient WebSocketClient { get; } | |||
| public ConnectionState ConnectionState { get; private set; } | |||
| @@ -43,14 +47,29 @@ namespace Discord.API | |||
| _isExplicitUrl = true; | |||
| WebSocketClient = webSocketProvider(); | |||
| //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) | |||
| WebSocketClient.BinaryMessage += async (data, index, count) => | |||
| { | |||
| using (var compressed = new MemoryStream(data, index + 2, count - 2)) | |||
| 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; | |||
| using (var reader = new StreamReader(decompressed)) | |||
| using (var jsonReader = new JsonTextReader(reader)) | |||
| { | |||
| @@ -76,6 +95,7 @@ namespace Discord.API | |||
| await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | |||
| }; | |||
| } | |||
| internal override void Dispose(bool disposing) | |||
| { | |||
| if (!_isDisposed) | |||
| @@ -84,6 +104,8 @@ namespace Discord.API | |||
| { | |||
| _connectCancelToken?.Dispose(); | |||
| (WebSocketClient as IDisposable)?.Dispose(); | |||
| _decompressor?.Dispose(); | |||
| _compressed?.Dispose(); | |||
| } | |||
| _isDisposed = true; | |||
| } | |||
| @@ -105,6 +127,12 @@ namespace Discord.API | |||
| if (WebSocketClient == null) | |||
| 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; | |||
| try | |||
| { | |||
| @@ -115,7 +143,7 @@ namespace Discord.API | |||
| if (!_isExplicitUrl) | |||
| { | |||
| 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); | |||
| @@ -191,7 +219,7 @@ namespace Discord.API | |||
| options = RequestOptions.CreateOrClone(options); | |||
| 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); | |||
| var props = new Dictionary<string, string> | |||
| @@ -202,8 +230,7 @@ namespace Discord.API | |||
| { | |||
| Token = AuthToken, | |||
| Properties = props, | |||
| LargeThreshold = largeThreshold, | |||
| UseCompression = useCompression, | |||
| LargeThreshold = largeThreshold | |||
| }; | |||
| if (totalShards > 1) | |||
| msg.ShardingParams = new int[] { shardID, totalShards }; | |||
| @@ -2,218 +2,37 @@ | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket | |||
| { | |||
| //TODO: Add event docstrings | |||
| { | |||
| public partial class DiscordSocketClient | |||
| { | |||
| //General | |||
| /// <summary> Fired when connected to the Discord gateway. </summary> | |||
| public event Func<Task> Connected | |||
| { | |||
| add { _connectedEvent.Add(value); } | |||
| remove { _connectedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | |||
| /// <summary> Fired when disconnected to the Discord gateway. </summary> | |||
| public event Func<Exception, Task> Disconnected | |||
| { | |||
| add { _disconnectedEvent.Add(value); } | |||
| remove { _disconnectedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | |||
| /// <summary> Fired when guild data has finished downloading. </summary> | |||
| public event Func<Task> Ready | |||
| { | |||
| add { _readyEvent.Add(value); } | |||
| remove { _readyEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>(); | |||
| /// <summary> Fired when a heartbeat is received from the Discord gateway. </summary> | |||
| public event Func<int, int, Task> LatencyUpdated | |||
| { | |||
| add { _latencyUpdatedEvent.Add(value); } | |||
| remove { _latencyUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<int, int, Task>> _latencyUpdatedEvent = new AsyncEvent<Func<int, int, Task>>(); | |||
| //Channels | |||
| public event Func<SocketChannel, Task> ChannelCreated | |||
| { | |||
| add { _channelCreatedEvent.Add(value); } | |||
| remove { _channelCreatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelCreatedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| public event Func<SocketChannel, Task> ChannelDestroyed | |||
| { | |||
| add { _channelDestroyedEvent.Add(value); } | |||
| remove { _channelDestroyedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, Task>> _channelDestroyedEvent = new AsyncEvent<Func<SocketChannel, Task>>(); | |||
| public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated | |||
| { | |||
| add { _channelUpdatedEvent.Add(value); } | |||
| remove { _channelUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketChannel, SocketChannel, Task>> _channelUpdatedEvent = new AsyncEvent<Func<SocketChannel, SocketChannel, Task>>(); | |||
| //Messages | |||
| public event Func<SocketMessage, Task> MessageReceived | |||
| { | |||
| add { _messageReceivedEvent.Add(value); } | |||
| remove { _messageReceivedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketMessage, Task>> _messageReceivedEvent = new AsyncEvent<Func<SocketMessage, Task>>(); | |||
| public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted | |||
| { | |||
| add { _messageDeletedEvent.Add(value); } | |||
| remove { _messageDeletedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated | |||
| { | |||
| add { _messageUpdatedEvent.Add(value); } | |||
| remove { _messageUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded | |||
| { | |||
| add { _reactionAddedEvent.Add(value); } | |||
| remove { _reactionAddedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved | |||
| { | |||
| add { _reactionRemovedEvent.Add(value); } | |||
| remove { _reactionRemovedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
| public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared | |||
| { | |||
| add { _reactionsClearedEvent.Add(value); } | |||
| remove { _reactionsClearedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
| //Roles | |||
| public event Func<SocketRole, Task> RoleCreated | |||
| { | |||
| add { _roleCreatedEvent.Add(value); } | |||
| remove { _roleCreatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| public event Func<SocketRole, Task> RoleDeleted | |||
| { | |||
| add { _roleDeletedEvent.Add(value); } | |||
| remove { _roleDeletedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>(); | |||
| public event Func<SocketRole, SocketRole, Task> RoleUpdated | |||
| { | |||
| add { _roleUpdatedEvent.Add(value); } | |||
| remove { _roleUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketRole, SocketRole, Task>> _roleUpdatedEvent = new AsyncEvent<Func<SocketRole, SocketRole, Task>>(); | |||
| //Guilds | |||
| public event Func<SocketGuild, Task> JoinedGuild | |||
| { | |||
| add { _joinedGuildEvent.Add(value); } | |||
| remove { _joinedGuildEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> LeftGuild | |||
| { | |||
| add { _leftGuildEvent.Add(value); } | |||
| remove { _leftGuildEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildAvailable | |||
| { | |||
| add { _guildAvailableEvent.Add(value); } | |||
| remove { _guildAvailableEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildUnavailable | |||
| { | |||
| add { _guildUnavailableEvent.Add(value); } | |||
| remove { _guildUnavailableEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, Task> GuildMembersDownloaded | |||
| { | |||
| add { _guildMembersDownloadedEvent.Add(value); } | |||
| remove { _guildMembersDownloadedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>(); | |||
| public event Func<SocketGuild, SocketGuild, Task> GuildUpdated | |||
| { | |||
| add { _guildUpdatedEvent.Add(value); } | |||
| remove { _guildUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>(); | |||
| //Users | |||
| public event Func<SocketGuildUser, Task> UserJoined | |||
| { | |||
| add { _userJoinedEvent.Add(value); } | |||
| remove { _userJoinedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| public event Func<SocketGuildUser, Task> UserLeft | |||
| { | |||
| add { _userLeftEvent.Add(value); } | |||
| remove { _userLeftEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>(); | |||
| public event Func<SocketUser, SocketGuild, Task> UserBanned | |||
| { | |||
| add { _userBannedEvent.Add(value); } | |||
| remove { _userBannedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| public event Func<SocketUser, SocketGuild, Task> UserUnbanned | |||
| { | |||
| add { _userUnbannedEvent.Add(value); } | |||
| remove { _userUnbannedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>(); | |||
| public event Func<SocketUser, SocketUser, Task> UserUpdated | |||
| { | |||
| add { _userUpdatedEvent.Add(value); } | |||
| remove { _userUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | |||
| public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated | |||
| { | |||
| add { _guildMemberUpdatedEvent.Add(value); } | |||
| remove { _guildMemberUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||
| public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated | |||
| { | |||
| add { _userVoiceStateUpdatedEvent.Add(value); } | |||
| remove { _userVoiceStateUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>> _userVoiceStateUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketVoiceState, SocketVoiceState, Task>>(); | |||
| public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated | |||
| { | |||
| add { _selfUpdatedEvent.Add(value); } | |||
| remove { _selfUpdatedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | |||
| public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping | |||
| { | |||
| add { _userIsTypingEvent.Add(value); } | |||
| remove { _userIsTypingEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||
| public event Func<SocketGroupUser, Task> RecipientAdded | |||
| { | |||
| add { _recipientAddedEvent.Add(value); } | |||
| remove { _recipientAddedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| public event Func<SocketGroupUser, Task> RecipientRemoved | |||
| { | |||
| add { _recipientRemovedEvent.Add(value); } | |||
| remove { _recipientRemovedEvent.Remove(value); } | |||
| } | |||
| private readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>(); | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| using Discord.API; | |||
| #pragma warning disable CS0618 | |||
| using Discord.API; | |||
| using Discord.API.Gateway; | |||
| using Discord.Logging; | |||
| using Discord.Net.Converters; | |||
| @@ -19,7 +20,7 @@ using GameModel = Discord.API.Game; | |||
| namespace Discord.WebSocket | |||
| { | |||
| public partial class DiscordSocketClient : BaseDiscordClient, IDiscordClient | |||
| public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient | |||
| { | |||
| private readonly ConcurrentQueue<ulong> _largeGuilds; | |||
| private readonly JsonSerializer _serializer; | |||
| @@ -44,10 +45,10 @@ namespace Discord.WebSocket | |||
| public int ShardId { get; } | |||
| /// <summary> Gets the current connection state of this client. </summary> | |||
| public ConnectionState ConnectionState => _connection.State; | |||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
| public int Latency { get; private set; } | |||
| internal UserStatus Status { get; private set; } = UserStatus.Online; | |||
| internal Game? Game { get; private set; } | |||
| /// <inheritdoc /> | |||
| public override int Latency { get; protected set; } | |||
| public override UserStatus Status { get; protected set; } = UserStatus.Online; | |||
| public override Game? Game { get; protected set; } | |||
| //From DiscordSocketConfig | |||
| internal int TotalShards { get; private set; } | |||
| @@ -60,14 +61,13 @@ namespace Discord.WebSocket | |||
| internal int? HandlerTimeout { get; private set; } | |||
| internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
| public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; private set => base.CurrentUser = value; } | |||
| public IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | |||
| public IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; | |||
| public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | |||
| public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; | |||
| public IReadOnlyCollection<SocketDMChannel> DMChannels | |||
| => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); | |||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
| => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
| public IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
| /// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
| public DiscordSocketClient() : this(new DiscordSocketConfig()) { } | |||
| @@ -155,9 +155,9 @@ namespace Discord.WebSocket | |||
| _voiceRegions = ImmutableDictionary.Create<string, RestVoiceRegion>(); | |||
| } | |||
| public async Task StartAsync() | |||
| public override async Task StartAsync() | |||
| => await _connection.StartAsync().ConfigureAwait(false); | |||
| public async Task StopAsync() | |||
| public override async Task StopAsync() | |||
| => await _connection.StopAsync().ConfigureAwait(false); | |||
| private async Task OnConnectingAsync() | |||
| @@ -231,44 +231,23 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <inheritdoc /> | |||
| public async Task<RestApplication> GetApplicationInfoAsync() | |||
| { | |||
| return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, new RequestOptions())); | |||
| } | |||
| /// <inheritdoc /> | |||
| public SocketGuild GetGuild(ulong id) | |||
| { | |||
| return State.GetGuild(id); | |||
| } | |||
| /// <inheritdoc /> | |||
| public Task<RestGuild> CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null) | |||
| => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, new RequestOptions()); | |||
| /// <inheritdoc /> | |||
| public SocketChannel GetChannel(ulong id) | |||
| { | |||
| return State.GetChannel(id); | |||
| } | |||
| public override async Task<RestApplication> GetApplicationInfoAsync(RequestOptions options = null) | |||
| => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); | |||
| /// <inheritdoc /> | |||
| public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync() | |||
| => ClientHelper.GetConnectionsAsync(this, new RequestOptions()); | |||
| public override SocketGuild GetGuild(ulong id) | |||
| => State.GetGuild(id); | |||
| /// <inheritdoc /> | |||
| public Task<RestInvite> GetInviteAsync(string inviteId) | |||
| => ClientHelper.GetInviteAsync(this, inviteId, new RequestOptions()); | |||
| public override SocketChannel GetChannel(ulong id) | |||
| => State.GetChannel(id); | |||
| /// <inheritdoc /> | |||
| public SocketUser GetUser(ulong id) | |||
| { | |||
| return State.GetUser(id); | |||
| } | |||
| public override SocketUser GetUser(ulong id) | |||
| => State.GetUser(id); | |||
| /// <inheritdoc /> | |||
| public SocketUser GetUser(string username, string discriminator) | |||
| { | |||
| return State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); | |||
| } | |||
| public override SocketUser GetUser(string username, string discriminator) | |||
| => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); | |||
| internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | |||
| { | |||
| return state.GetOrAddUser(model.Id, x => | |||
| @@ -288,13 +267,11 @@ namespace Discord.WebSocket | |||
| return user; | |||
| }); | |||
| } | |||
| internal void RemoveUser(ulong id) | |||
| { | |||
| State.RemoveUser(id); | |||
| } | |||
| internal void RemoveUser(ulong id) | |||
| => State.RemoveUser(id); | |||
| /// <inheritdoc /> | |||
| public RestVoiceRegion GetVoiceRegion(string id) | |||
| public override RestVoiceRegion GetVoiceRegion(string id) | |||
| { | |||
| if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) | |||
| return region; | |||
| @@ -302,7 +279,7 @@ namespace Discord.WebSocket | |||
| } | |||
| /// <summary> Downloads the users list for the provided guilds, if they don't have a complete list. </summary> | |||
| public async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | |||
| public override async Task DownloadUsersAsync(IEnumerable<IGuild> guilds) | |||
| { | |||
| if (ConnectionState == ConnectionState.Connected) | |||
| { | |||
| @@ -340,7 +317,7 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| public async Task SetStatusAsync(UserStatus status) | |||
| public override async Task SetStatusAsync(UserStatus status) | |||
| { | |||
| Status = status; | |||
| if (status == UserStatus.AFK) | |||
| @@ -349,7 +326,7 @@ namespace Discord.WebSocket | |||
| _statusSince = null; | |||
| await SendStatusAsync().ConfigureAwait(false); | |||
| } | |||
| public async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) | |||
| public override async Task SetGameAsync(string name, string streamUrl = null, StreamType streamType = StreamType.NotStreaming) | |||
| { | |||
| if (name != null) | |||
| Game = new Game(name, streamUrl, streamType); | |||
| @@ -76,11 +76,6 @@ namespace Discord.WebSocket | |||
| 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); | |||
| 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| 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) | |||
| => 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) | |||
| => ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
| public IDisposable EnterTypingState(RequestOptions options = null) | |||
| @@ -119,7 +119,7 @@ namespace Discord.WebSocket | |||
| public async Task<IReadOnlyCollection<RestInviteMetadata>> GetInvitesAsync(RequestOptions options = null) | |||
| => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 3600, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| public async Task<RestInviteMetadata> CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) | |||
| => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); | |||
| public new virtual SocketGuildUser GetUser(ulong id) => null; | |||
| @@ -160,8 +160,8 @@ namespace Discord.WebSocket | |||
| //IChannel | |||
| IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overriden in Text/Voice | |||
| => ImmutableArray.Create<IReadOnlyCollection<IUser>>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice | |||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
| => Task.FromResult<IUser>(GetUser(id)); //Overriden in Text/Voice | |||
| => Task.FromResult<IUser>(GetUser(id)); //Overridden in Text/Voice | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| using Discord.Audio; | |||
| #pragma warning disable CS0618 | |||
| using Discord.Audio; | |||
| using Discord.Rest; | |||
| using System; | |||
| using System.Collections.Concurrent; | |||
| @@ -437,6 +438,16 @@ namespace Discord.WebSocket | |||
| _downloaderPromise.TrySetResultAsync(true); | |||
| } | |||
| //Emotes | |||
| public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | |||
| => GuildHelper.GetEmoteAsync(this, Discord, id, options); | |||
| public Task<GuildEmote> CreateEmoteAsync(string name, Image image, Optional<IEnumerable<IRole>> roles = default(Optional<IEnumerable<IRole>>), RequestOptions options = null) | |||
| => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); | |||
| public Task<GuildEmote> ModifyEmoteAsync(GuildEmote emote, Action<EmoteProperties> func, RequestOptions options = null) | |||
| => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); | |||
| public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) | |||
| => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); | |||
| //Voice States | |||
| internal async Task<SocketVoiceState> AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) | |||
| { | |||
| @@ -43,7 +43,7 @@ namespace Discord.WebSocket | |||
| public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); | |||
| public AudioInStream AudioStream => Guild.GetAudioStream(Id); | |||
| /// <summary> The position of the user within the role hirearchy. </summary> | |||
| /// <summary> The position of the user within the role hierarchy. </summary> | |||
| /// <remarks> The returned value equal to the position of the highest role the user has, | |||
| /// or int.MaxValue if user is the server owner. </remarks> | |||
| public int Hierarchy | |||
| @@ -3,6 +3,7 @@ using System; | |||
| using System.Collections.Generic; | |||
| using System.ComponentModel; | |||
| using System.IO; | |||
| using System.Net; | |||
| using System.Net.WebSockets; | |||
| using System.Text; | |||
| using System.Threading; | |||
| @@ -23,18 +24,20 @@ namespace Discord.Net.WebSockets | |||
| private readonly SemaphoreSlim _lock; | |||
| private readonly Dictionary<string, string> _headers; | |||
| private ClientWebSocket _client; | |||
| private IWebProxy _proxy; | |||
| private Task _task; | |||
| private CancellationTokenSource _cancelTokenSource; | |||
| private CancellationToken _cancelToken, _parentToken; | |||
| private bool _isDisposed, _isDisconnecting; | |||
| public DefaultWebSocketClient() | |||
| public DefaultWebSocketClient(IWebProxy proxy = null) | |||
| { | |||
| _lock = new SemaphoreSlim(1, 1); | |||
| _cancelTokenSource = new CancellationTokenSource(); | |||
| _cancelToken = CancellationToken.None; | |||
| _parentToken = CancellationToken.None; | |||
| _headers = new Dictionary<string, string>(); | |||
| _proxy = proxy; | |||
| } | |||
| private void Dispose(bool disposing) | |||
| { | |||
| @@ -70,7 +73,7 @@ namespace Discord.Net.WebSockets | |||
| _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; | |||
| _client = new ClientWebSocket(); | |||
| _client.Options.Proxy = null; | |||
| _client.Options.Proxy = _proxy; | |||
| _client.Options.KeepAliveInterval = TimeSpan.Zero; | |||
| foreach (var header in _headers) | |||
| { | |||
| @@ -1,21 +1,27 @@ | |||
| using System; | |||
| using System.Net; | |||
| namespace Discord.Net.WebSockets | |||
| { | |||
| public static class DefaultWebSocketProvider | |||
| { | |||
| #if DEFAULTWEBSOCKET | |||
| public static readonly WebSocketProvider Instance = () => | |||
| public static readonly WebSocketProvider Instance = Create(); | |||
| public static WebSocketProvider Create(IWebProxy proxy = null) | |||
| { | |||
| try | |||
| { | |||
| return new DefaultWebSocketClient(); | |||
| } | |||
| catch (PlatformNotSupportedException ex) | |||
| return () => | |||
| { | |||
| throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex); | |||
| } | |||
| }; | |||
| try | |||
| { | |||
| return new DefaultWebSocketClient(proxy); | |||
| } | |||
| catch (PlatformNotSupportedException ex) | |||
| { | |||
| throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex); | |||
| } | |||
| }; | |||
| } | |||
| #else | |||
| public static readonly WebSocketProvider Instance = () => | |||
| { | |||
| @@ -2,7 +2,7 @@ | |||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | |||
| <metadata> | |||
| <id>Discord.Net</id> | |||
| <version>2.0.0-alpha$suffix$</version> | |||
| <version>2.0.0-beta$suffix$</version> | |||
| <title>Discord.Net</title> | |||
| <authors>Discord.Net Contributors</authors> | |||
| <owners>RogueException</owners> | |||
| @@ -13,28 +13,28 @@ | |||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | |||
| <dependencies> | |||
| <group targetFramework="net45"> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" /> | |||
| </group> | |||
| <group targetFramework="netstandard1.1"> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" /> | |||
| </group> | |||
| <group targetFramework="netstandard1.3"> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-alpha$suffix$" /> | |||
| <dependency id="Discord.Net.Core" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rest" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.WebSocket" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Rpc" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Commands" version="2.0.0-beta$suffix$" /> | |||
| <dependency id="Discord.Net.Webhook" version="2.0.0-beta$suffix$" /> | |||
| </group> | |||
| </dependencies> | |||
| </metadata> | |||
| @@ -0,0 +1,324 @@ | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| using Xunit; | |||
| namespace Discord | |||
| { | |||
| public partial class Tests | |||
| { | |||
| [Fact] | |||
| public void TestChannelPermission() | |||
| { | |||
| var perm = new ChannelPermissions(); | |||
| // check initial values | |||
| Assert.Equal((ulong)0, perm.RawValue); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // permissions list empty by default | |||
| Assert.Empty(perm.ToList()); | |||
| // test modify with no parameters | |||
| var copy = perm.Modify(); | |||
| Assert.Equal((ulong)0, copy.RawValue); | |||
| // test the values that are returned by ChannelPermission.All | |||
| Assert.Equal((ulong)0, ChannelPermissions.None.RawValue); | |||
| // for text channels | |||
| ulong textChannel = (ulong)( ChannelPermission.CreateInstantInvite | |||
| | ChannelPermission.ManageChannels | |||
| | ChannelPermission.AddReactions | |||
| | ChannelPermission.ReadMessages | |||
| | ChannelPermission.SendMessages | |||
| | ChannelPermission.SendTTSMessages | |||
| | ChannelPermission.ManageMessages | |||
| | ChannelPermission.EmbedLinks | |||
| | ChannelPermission.AttachFiles | |||
| | ChannelPermission.ReadMessageHistory | |||
| | ChannelPermission.MentionEveryone | |||
| | ChannelPermission.UseExternalEmojis | |||
| | ChannelPermission.ManageRoles | |||
| | ChannelPermission.ManageWebhooks); | |||
| Assert.Equal(textChannel, ChannelPermissions.Text.RawValue); | |||
| // voice channels | |||
| ulong voiceChannel = (ulong)( | |||
| ChannelPermission.CreateInstantInvite | |||
| | ChannelPermission.ManageChannels | |||
| | ChannelPermission.Connect | |||
| | ChannelPermission.Speak | |||
| | ChannelPermission.MuteMembers | |||
| | ChannelPermission.DeafenMembers | |||
| | ChannelPermission.MoveMembers | |||
| | ChannelPermission.UseVAD | |||
| | ChannelPermission.ManageRoles); | |||
| Assert.Equal(voiceChannel, ChannelPermissions.Voice.RawValue); | |||
| // DM Channels | |||
| ulong dmChannel = (ulong)( | |||
| ChannelPermission.ReadMessages | |||
| | ChannelPermission.SendMessages | |||
| | ChannelPermission.EmbedLinks | |||
| | ChannelPermission.AttachFiles | |||
| | ChannelPermission.ReadMessageHistory | |||
| | ChannelPermission.UseExternalEmojis | |||
| | ChannelPermission.Connect | |||
| | ChannelPermission.Speak | |||
| | ChannelPermission.UseVAD | |||
| ); | |||
| Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue); | |||
| // group channel | |||
| ulong groupChannel = (ulong)( | |||
| ChannelPermission.SendMessages | |||
| | ChannelPermission.EmbedLinks | |||
| | ChannelPermission.AttachFiles | |||
| | ChannelPermission.SendTTSMessages | |||
| | ChannelPermission.Connect | |||
| | ChannelPermission.Speak | |||
| | ChannelPermission.UseVAD | |||
| ); | |||
| Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue); | |||
| } | |||
| public void TestChannelPermissionModify() | |||
| { | |||
| // test channel permission modify | |||
| var perm = new ChannelPermissions(); | |||
| // ensure that the permission is initially false | |||
| Assert.False(perm.CreateInstantInvite); | |||
| // ensure that when modified it works | |||
| perm = perm.Modify(createInstantInvite: true); | |||
| Assert.True(perm.CreateInstantInvite); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite); | |||
| // set false again, move on to next permission | |||
| perm = perm.Modify(createInstantInvite: false); | |||
| Assert.False(perm.CreateInstantInvite); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ManageChannel); | |||
| perm = perm.Modify(manageChannel: true); | |||
| Assert.True(perm.ManageChannel); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels); | |||
| perm = perm.Modify(manageChannel: false); | |||
| Assert.False(perm.ManageChannel); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.AddReactions); | |||
| perm = perm.Modify(addReactions: true); | |||
| Assert.True(perm.AddReactions); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions); | |||
| perm = perm.Modify(addReactions: false); | |||
| Assert.False(perm.AddReactions); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ReadMessages); | |||
| perm = perm.Modify(readMessages: true); | |||
| Assert.True(perm.ReadMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages); | |||
| perm = perm.Modify(readMessages: false); | |||
| Assert.False(perm.ReadMessages); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.SendMessages); | |||
| perm = perm.Modify(sendMessages: true); | |||
| Assert.True(perm.SendMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages); | |||
| perm = perm.Modify(sendMessages: false); | |||
| Assert.False(perm.SendMessages); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.SendTTSMessages); | |||
| perm = perm.Modify(sendTTSMessages: true); | |||
| Assert.True(perm.SendTTSMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendTTSMessages); | |||
| perm = perm.Modify(sendTTSMessages: false); | |||
| Assert.False(perm.SendTTSMessages); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ManageMessages); | |||
| perm = perm.Modify(manageMessages: true); | |||
| Assert.True(perm.ManageMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageMessages); | |||
| perm = perm.Modify(manageMessages: false); | |||
| Assert.False(perm.ManageMessages); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.EmbedLinks); | |||
| perm = perm.Modify(embedLinks: true); | |||
| Assert.True(perm.EmbedLinks); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks); | |||
| perm = perm.Modify(embedLinks: false); | |||
| Assert.False(perm.EmbedLinks); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.AttachFiles); | |||
| perm = perm.Modify(attachFiles: true); | |||
| Assert.True(perm.AttachFiles); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles); | |||
| perm = perm.Modify(attachFiles: false); | |||
| Assert.False(perm.AttachFiles); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ReadMessageHistory); | |||
| perm = perm.Modify(readMessageHistory: true); | |||
| Assert.True(perm.ReadMessageHistory); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory); | |||
| perm = perm.Modify(readMessageHistory: false); | |||
| Assert.False(perm.ReadMessageHistory); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.MentionEveryone); | |||
| perm = perm.Modify(mentionEveryone: true); | |||
| Assert.True(perm.MentionEveryone); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone); | |||
| perm = perm.Modify(mentionEveryone: false); | |||
| Assert.False(perm.MentionEveryone); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.UseExternalEmojis); | |||
| perm = perm.Modify(useExternalEmojis: true); | |||
| Assert.True(perm.UseExternalEmojis); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis); | |||
| perm = perm.Modify(useExternalEmojis: false); | |||
| Assert.False(perm.UseExternalEmojis); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.Connect); | |||
| perm = perm.Modify(connect: true); | |||
| Assert.True(perm.Connect); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect); | |||
| perm = perm.Modify(connect: false); | |||
| Assert.False(perm.Connect); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.Speak); | |||
| perm = perm.Modify(speak: true); | |||
| Assert.True(perm.Speak); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak); | |||
| perm = perm.Modify(speak: false); | |||
| Assert.False(perm.Speak); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.MuteMembers); | |||
| perm = perm.Modify(muteMembers: true); | |||
| Assert.True(perm.MuteMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers); | |||
| perm = perm.Modify(muteMembers: false); | |||
| Assert.False(perm.MuteMembers); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.DeafenMembers); | |||
| perm = perm.Modify(deafenMembers: true); | |||
| Assert.True(perm.DeafenMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers); | |||
| perm = perm.Modify(deafenMembers: false); | |||
| Assert.False(perm.DeafenMembers); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.MoveMembers); | |||
| perm = perm.Modify(moveMembers: true); | |||
| Assert.True(perm.MoveMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers); | |||
| perm = perm.Modify(moveMembers: false); | |||
| Assert.False(perm.MoveMembers); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.UseVAD); | |||
| perm = perm.Modify(useVoiceActivation: true); | |||
| Assert.True(perm.UseVAD); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD); | |||
| perm = perm.Modify(useVoiceActivation: false); | |||
| Assert.False(perm.UseVAD); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ManageRoles); | |||
| perm = perm.Modify(manageRoles: true); | |||
| Assert.True(perm.ManageRoles); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles); | |||
| perm = perm.Modify(manageRoles: false); | |||
| Assert.False(perm.ManageRoles); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| Assert.False(perm.ManageWebhooks); | |||
| perm = perm.Modify(manageWebhooks: true); | |||
| Assert.True(perm.ManageWebhooks); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks); | |||
| perm = perm.Modify(manageWebhooks: false); | |||
| Assert.False(perm.ManageWebhooks); | |||
| Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); | |||
| } | |||
| [Fact] | |||
| public void TestChannelTypeResolution() | |||
| { | |||
| ITextChannel someChannel = null; | |||
| // null channels will throw exception | |||
| Assert.Throws<ArgumentException>(() => ChannelPermissions.All(someChannel)); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,304 @@ | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| using Xunit; | |||
| namespace Discord | |||
| { | |||
| public partial class Tests | |||
| { | |||
| [Fact] | |||
| public void TestGuildPermission() | |||
| { | |||
| // Test Guild Permission Constructors | |||
| var perm = new GuildPermissions(); | |||
| // the default raw value is 0 | |||
| Assert.Equal((ulong)0, perm.RawValue); | |||
| // also check that it is the same as none | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // permissions list is empty by default | |||
| Assert.Empty(perm.ToList()); | |||
| Assert.NotNull(perm.ToList()); | |||
| // Test modify with no parameters | |||
| var copy = perm.Modify(); | |||
| // ensure that the raw values match | |||
| Assert.Equal((ulong)0, copy.RawValue); | |||
| // test GuildPermissions.All | |||
| ulong sumOfAllGuildPermissions = 0; | |||
| foreach(var v in Enum.GetValues(typeof(GuildPermission))) | |||
| { | |||
| sumOfAllGuildPermissions |= (ulong)v; | |||
| } | |||
| // assert that the raw values match | |||
| Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue); | |||
| Assert.Equal((ulong)0, GuildPermissions.None.RawValue); | |||
| // assert that GuildPermissions.All contains the same number of permissions as the | |||
| // GuildPermissions enum | |||
| Assert.Equal(Enum.GetValues(typeof(GuildPermission)).Length, GuildPermissions.All.ToList().Count); | |||
| // assert that webhook has the same raw value | |||
| ulong webHookPermissions = (ulong)( | |||
| GuildPermission.SendMessages | GuildPermission.SendTTSMessages | GuildPermission.EmbedLinks | | |||
| GuildPermission.AttachFiles); | |||
| Assert.Equal(webHookPermissions, GuildPermissions.Webhook.RawValue); | |||
| } | |||
| [Fact] | |||
| public void TestGuildPermissionModify() | |||
| { | |||
| var perm = new GuildPermissions(); | |||
| // tests each of the parameters of Modify one by one | |||
| // test modify with each of the parameters | |||
| // test initially false state | |||
| Assert.False(perm.CreateInstantInvite); | |||
| // ensure that when we modify it the parameter works | |||
| perm = perm.Modify(createInstantInvite: true); | |||
| Assert.True(perm.CreateInstantInvite); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.CreateInstantInvite); | |||
| // set it false again, then move on to the next permission | |||
| perm = perm.Modify(createInstantInvite: false); | |||
| Assert.False(perm.CreateInstantInvite); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(kickMembers: true); | |||
| Assert.True(perm.KickMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.KickMembers); | |||
| perm = perm.Modify(kickMembers: false); | |||
| Assert.False(perm.KickMembers); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(banMembers: true); | |||
| Assert.True(perm.BanMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.BanMembers); | |||
| perm = perm.Modify(banMembers: false); | |||
| Assert.False(perm.BanMembers); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(administrator: true); | |||
| Assert.True(perm.Administrator); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.Administrator); | |||
| perm = perm.Modify(administrator: false); | |||
| Assert.False(perm.Administrator); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageChannels: true); | |||
| Assert.True(perm.ManageChannels); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageChannels); | |||
| perm = perm.Modify(manageChannels: false); | |||
| Assert.False(perm.ManageChannels); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageGuild: true); | |||
| Assert.True(perm.ManageGuild); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageGuild); | |||
| perm = perm.Modify(manageGuild: false); | |||
| Assert.False(perm.ManageGuild); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(addReactions: true); | |||
| Assert.True(perm.AddReactions); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.AddReactions); | |||
| perm = perm.Modify(addReactions: false); | |||
| Assert.False(perm.AddReactions); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(viewAuditLog: true); | |||
| Assert.True(perm.ViewAuditLog); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ViewAuditLog); | |||
| perm = perm.Modify(viewAuditLog: false); | |||
| Assert.False(perm.ViewAuditLog); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(readMessages: true); | |||
| Assert.True(perm.ReadMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessages); | |||
| perm = perm.Modify(readMessages: false); | |||
| Assert.False(perm.ReadMessages); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(sendMessages: true); | |||
| Assert.True(perm.SendMessages); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.SendMessages); | |||
| perm = perm.Modify(sendMessages: false); | |||
| Assert.False(perm.SendMessages); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(embedLinks: true); | |||
| Assert.True(perm.EmbedLinks); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.EmbedLinks); | |||
| perm = perm.Modify(embedLinks: false); | |||
| Assert.False(perm.EmbedLinks); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(attachFiles: true); | |||
| Assert.True(perm.AttachFiles); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.AttachFiles); | |||
| perm = perm.Modify(attachFiles: false); | |||
| Assert.False(perm.AttachFiles); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(readMessageHistory: true); | |||
| Assert.True(perm.ReadMessageHistory); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ReadMessageHistory); | |||
| perm = perm.Modify(readMessageHistory: false); | |||
| Assert.False(perm.ReadMessageHistory); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(mentionEveryone: true); | |||
| Assert.True(perm.MentionEveryone); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MentionEveryone); | |||
| perm = perm.Modify(mentionEveryone: false); | |||
| Assert.False(perm.MentionEveryone); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(useExternalEmojis: true); | |||
| Assert.True(perm.UseExternalEmojis); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseExternalEmojis); | |||
| perm = perm.Modify(useExternalEmojis: false); | |||
| Assert.False(perm.UseExternalEmojis); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(connect: true); | |||
| Assert.True(perm.Connect); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.Connect); | |||
| perm = perm.Modify(connect: false); | |||
| Assert.False(perm.Connect); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(speak: true); | |||
| Assert.True(perm.Speak); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.Speak); | |||
| perm = perm.Modify(speak: false); | |||
| Assert.False(perm.Speak); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(muteMembers: true); | |||
| Assert.True(perm.MuteMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MuteMembers); | |||
| perm = perm.Modify(muteMembers: false); | |||
| Assert.False(perm.MuteMembers); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(deafenMembers: true); | |||
| Assert.True(perm.DeafenMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.DeafenMembers); | |||
| perm = perm.Modify(deafenMembers: false); | |||
| Assert.False(perm.DeafenMembers); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(moveMembers: true); | |||
| Assert.True(perm.MoveMembers); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.MoveMembers); | |||
| perm = perm.Modify(moveMembers: false); | |||
| Assert.False(perm.MoveMembers); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(useVoiceActivation: true); | |||
| Assert.True(perm.UseVAD); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.UseVAD); | |||
| perm = perm.Modify(useVoiceActivation: false); | |||
| Assert.False(perm.UseVAD); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(changeNickname: true); | |||
| Assert.True(perm.ChangeNickname); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ChangeNickname); | |||
| perm = perm.Modify(changeNickname: false); | |||
| Assert.False(perm.ChangeNickname); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageNicknames: true); | |||
| Assert.True(perm.ManageNicknames); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageNicknames); | |||
| perm = perm.Modify(manageNicknames: false); | |||
| Assert.False(perm.ManageNicknames); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageRoles: true); | |||
| Assert.True(perm.ManageRoles); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageRoles); | |||
| perm = perm.Modify(manageRoles: false); | |||
| Assert.False(perm.ManageRoles); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageWebhooks: true); | |||
| Assert.True(perm.ManageWebhooks); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageWebhooks); | |||
| perm = perm.Modify(manageWebhooks: false); | |||
| Assert.False(perm.ManageWebhooks); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| // individual permission test | |||
| perm = perm.Modify(manageEmojis: true); | |||
| Assert.True(perm.ManageEmojis); | |||
| Assert.Equal(perm.RawValue, (ulong)GuildPermission.ManageEmojis); | |||
| perm = perm.Modify(manageEmojis: false); | |||
| Assert.False(perm.ManageEmojis); | |||
| Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); | |||
| } | |||
| } | |||
| } | |||