diff --git a/Discord.Net.sln b/Discord.Net.sln index a63606787..58bfcad86 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -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 diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/commands.md index e021b1eb3..6781764c9 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/commands.md @@ -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_ \ No newline at end of file diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index 6b5d4ad2b..da2453aa8 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -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); } -} +} \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs index e205d891d..a36925904 100644 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ b/docs/guides/commands/samples/dependency_map_setup.cs @@ -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() + .BuildServiceProvider(); + // ... + await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); +} \ No newline at end of file diff --git a/docs/guides/commands/samples/empty-module.cs b/docs/guides/commands/samples/empty-module.cs index cac9922b5..6483c7cd2 100644 --- a/docs/guides/commands/samples/empty-module.cs +++ b/docs/guides/commands/samples/empty-module.cs @@ -1,6 +1,6 @@ using Discord.Commands; -public class InfoModule : ModuleBase +public class InfoModule : ModuleBase { } \ No newline at end of file diff --git a/docs/guides/commands/samples/groups.cs b/docs/guides/commands/samples/groups.cs index db6456c87..5f96c34e8 100644 --- a/docs/guides/commands/samples/groups.cs +++ b/docs/guides/commands/samples/groups.cs @@ -1,8 +1,8 @@ [Group("admin")] -public class AdminModule : ModuleBase +public class AdminModule : ModuleBase { [Group("clean")] - public class CleanModule : ModuleBase + public class CleanModule : ModuleBase { // ~admin clean 15 [Command] diff --git a/docs/guides/commands/samples/module.cs b/docs/guides/commands/samples/module.cs index 403acba06..5014619da 100644 --- a/docs/guides/commands/samples/module.cs +++ b/docs/guides/commands/samples/module.cs @@ -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 { - // ~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 { - // ~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}"); + } +} \ No newline at end of file diff --git a/docs/guides/concepts/entities.md b/docs/guides/concepts/entities.md index a38651829..3a5d5496b 100644 --- a/docs/guides/concepts/entities.md +++ b/docs/guides/concepts/entities.md @@ -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`. \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/guides/concepts/events.md b/docs/guides/concepts/events.md index f2dfb00f0..47db49aa8 100644 --- a/docs/guides/concepts/events.md +++ b/docs/guides/concepts/events.md @@ -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` 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. diff --git a/docs/guides/concepts/logging.md b/docs/guides/concepts/logging.md index 1592dfc72..50d2e9546 100644 --- a/docs/guides/concepts/logging.md +++ b/docs/guides/concepts/logging.md @@ -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)] diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index 82d242647..5d4c85d81 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -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' -![Step 3](images/install-vs-deps.png) -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." +![Step 3](images/install-vs-deps.png) +4. In the "Browse" tab, search for `Discord.Net`. +5. Install the `Discord.Net` package. ![Step 5](images/install-vs-nuget.png) ## Using JetBrains Rider -1. Create a new solution for your bot -2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for Solution) -![Step 2](images/install-rider-nuget-manager.png) -3. In the 'Packages' tab, search for 'Discord.Net' -![Step 3](images/install-rider-search.png) - > [!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). +![Step 2](images/install-rider-nuget-manager.png) +3. In the "Packages" tab, search for `Discord.Net`. +![Step 3](images/install-rider-search.png) +4. Install by adding the package to your project. ![Step 4](images/install-rider-add.png) ## 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)] diff --git a/docs/guides/getting_started/intro.md b/docs/guides/getting_started/intro.md index 837814511..02f04bec4 100644 --- a/docs/guides/getting_started/intro.md +++ b/docs/guides/getting_started/intro.md @@ -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 -![Step 4](images/intro-create-app.png) -5. In the application review page, click **Create a Bot User** -![Step 5](images/intro-create-bot.png) -6. Confirm the popup -7. If this bot will be public, check 'Public Bot'. -**Do not tick any other options!** +4. Create the Application. + + ![Step 4](images/intro-create-app.png) + +5. In the application review page, click **Create a Bot User**. + + ![Step 5](images/intro-create-bot.png) + +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**. - -![Step 2](images/intro-client-id.png) - + + ![Step 2](images/intro-client-id.png) + 3. Create an OAuth2 authorization URL `https://discordapp.com/oauth2/authorize?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. + + ![Step 6](images/intro-add-bot.png) -6. Click authorize - -![Step 6](images/intro-add-bot.png) ## 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 immidiately +jump into an async context. This will allow us to create a connection +to Discord later on without needing to worry about setting up the correct async implementation. >[!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]). + ![Token](images/intro-token.png) >[!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 \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/message.cs b/docs/guides/getting_started/samples/intro/message.cs index d3cda46e5..d6fd90778 100644 --- a/docs/guides/getting_started/samples/intro/message.cs +++ b/docs/guides/getting_started/samples/intro/message.cs @@ -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!"); } -} \ No newline at end of file +} diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 00ce7a6c9..789ceff76 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -8,12 +8,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 +16,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 +49,6 @@ class Program // that ask for a Func. private static Task Logger(LogMessage message) { - var cc = Console.ForegroundColor; switch (message.Severity) { case LogSeverity.Critical: @@ -66,8 +66,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 @@ -105,10 +105,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(); + // 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 +121,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 diff --git a/docs/guides/getting_started/terminology.md b/docs/guides/getting_started/terminology.md index a51003e34..74f7a6259 100644 --- a/docs/guides/getting_started/terminology.md +++ b/docs/guides/getting_started/terminology.md @@ -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`. \ No newline at end of file +`Discord.Net.WebSocket` provides a set of concrete classes that are +used primarily with Discord's WebSocket API or entities that are kept +in cache. When developing bots, you will be using this implementation. +All entities are prefixed with `Socket` (e.g. `SocketChannel`). \ No newline at end of file diff --git a/docs/guides/voice/sending-voice.md b/docs/guides/voice/sending-voice.md index c3ec8d9d7..024a98b95 100644 --- a/docs/guides/voice/sending-voice.md +++ b/docs/guides/voice/sending-voice.md @@ -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 diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 5dcd50cd8..b53b0248c 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -2,17 +2,18 @@ { public class CommandServiceConfig { - /// The default RunMode commands should have, if one is not specified on the Command attribute or builder. + /// Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. public RunMode DefaultRunMode { get; set; } = RunMode.Sync; public char SeparatorChar { get; set; } = ' '; - /// Should commands be case-sensitive? + + /// Determines whether commands should be case-sensitive. public bool CaseSensitiveCommands { get; set; } = false; /// Gets or sets the minimum log level severity that will be sent to the Log event. public LogSeverity LogLevel { get; set; } = LogSeverity.Info; - /// Gets or sets whether RunMode.Sync commands should push exceptions up to the caller. + /// Determines whether RunMode.Sync commands should push exceptions up to the caller. public bool ThrowOnError { get; set; } = true; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index a465b3ad8..a1df5b4c7 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -29,10 +29,6 @@ namespace Discord CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(RequestOptions options = null); - /// Bulk deletes multiple messages. - Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); - /// Bulk deletes multiple messages. - Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. Task TriggerTypingAsync(RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 0005737ed..7c6ec3908 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -13,6 +13,11 @@ namespace Discord /// Gets the current topic for this text channel. string Topic { get; } + /// Bulk deletes multiple messages. + Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); + /// Bulk deletes multiple messages. + Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); + /// Modifies this text channel. Task ModifyAsync(Action func, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index a93f02497..3e438f43f 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -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, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 94596e0e6..22e85263c 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -36,7 +36,7 @@ namespace Discord /// If True, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); /// If True, a user may create, delete and modify this channel. - public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannel); + public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); @@ -72,8 +72,8 @@ namespace Discord /// If True, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); - /// If True, a user may adjust permissions. This also implictly grants all other permissions. - public bool ManagePermissions => Permissions.GetValue(RawValue, ChannelPermission.ManagePermissions); + /// If True, a user may adjust role permissions. This also implictly grants all other permissions. + public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. 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) { } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. @@ -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 ToList() { var perms = new List(); - 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; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 3975c1b8b..8469fd304 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -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 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index c5f1efab0..030ccd587 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -11,7 +11,7 @@ namespace Discord /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a GuildPermissions that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110001_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110011_111111); /// Gets a packed value representing all the permissions in this GuildPermissions. public ulong RawValue { get; } @@ -31,6 +31,9 @@ namespace Discord /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); + /// If true, a user may view the audit log. + public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); + /// If True, a user may join channels. public bool ReadMessages => Permissions.GetValue(RawValue, GuildPermission.ReadMessages); /// If True, a user may send messages. @@ -78,11 +81,11 @@ namespace Discord 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? 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? userExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? useExternalEmojis = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) { @@ -92,9 +95,10 @@ namespace Discord Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); Permissions.SetValue(ref value, 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); @@ -122,26 +126,26 @@ namespace Discord /// Creates a new GuildPermissions with the provided permissions. 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 manageRoles = false, bool manageWebhooks = false, bool manageEmojis = false) - : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, + : this(0, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, manageWebhooks, manageEmojis) { } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. 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? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) - => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, + => new GuildPermissions(RawValue, createInstantInvite, manageRoles, kickMembers, banMembers, manageChannels, manageGuild, addReactions, viewAuditLog, readMessages, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); @@ -151,11 +155,14 @@ namespace Discord public List ToList() { var perms = new List(); - 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; } diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index c3f8b2bab..c3e296e2c 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -23,7 +23,7 @@ namespace Discord /// If Allowed, a user may create invites. public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); /// If Allowed, a user may create, delete and modify this channel. - public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannel); + public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannels); /// If Allowed, a user may add reactions. public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); /// If Allowed, a user may join channels. @@ -58,8 +58,8 @@ namespace Discord /// If Allowed, a user may use voice-activity-detection rather than push-to-talk. public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); - /// If Allowed, a user may adjust permissions. This also implictly grants all other permissions. - public PermValue ManagePermissions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManagePermissions); + /// If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. + public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); /// If True, a user may edit the webhooks for this channel. 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) { } /// Creates a new OverwritePermissions from this one, changing the provided non-null permissions. 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 ToAllowList() { var perms = new List(); - 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 ToDenyList() { var perms = new List(); - 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; } diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index c2b7e83ea..a7de90623 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -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); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index ed22cb25a..fa870be17 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -182,7 +182,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 messageIds, RequestOptions options) { var msgs = messageIds.ToArray(); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 8a31da3f1..d41441967 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -72,11 +72,6 @@ namespace Discord.Rest public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 44c118fee..aa5c0f7dc 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -85,11 +85,6 @@ namespace Discord.Rest public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 7043c8c76..eb807423f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -42,11 +42,6 @@ namespace Discord.Rest public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs index da9bce700..42e590aca 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcDMChannel.cs @@ -53,11 +53,6 @@ namespace Discord.Rpc public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs index d449688a4..1c9355047 100644 --- a/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs +++ b/src/Discord.Net.Rpc/Entities/Channels/RpcGroupChannel.cs @@ -56,11 +56,6 @@ namespace Discord.Rpc public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index e87c58221..af16f22f5 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -13,8 +13,6 @@ namespace Discord.API.Gateway public IDictionary Properties { get; set; } [JsonProperty("large_threshold")] public int LargeThreshold { get; set; } - [JsonProperty("compress")] - public bool UseCompression { get; set; } [JsonProperty("shard")] public Optional ShardingParams { get; set; } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 7d680eaf2..72781204c 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -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("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 @@ -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 }; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 322a99496..00cef60f8 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -76,11 +76,6 @@ namespace Discord.WebSocket public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index dc1853e73..92a93a903 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -104,11 +104,6 @@ namespace Discord.WebSocket public Task 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 messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable 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)