| @@ -1,10 +0,0 @@ | |||||
| ############### | |||||
| # folder # | |||||
| ############### | |||||
| /**/DROP/ | |||||
| /**/TEMP/ | |||||
| /**/packages/ | |||||
| /**/bin/ | |||||
| /**/obj/ | |||||
| _site | |||||
| @@ -1,46 +0,0 @@ | |||||
| # Contributing to Docs | |||||
| I don't really have any strict conditions for writing documentation, | |||||
| but just keep these few guidelines in mind: | |||||
| * Keep code samples in the `guides/samples` folder | |||||
| * When referencing an object in the API, link to it's page in the | |||||
| API documentation. | |||||
| * Documentation should be written in clear and proper English* | |||||
| \* If anyone is interested in translating documentation into other | |||||
| languages, please open an issue or contact me on | |||||
| Discord (`foxbot#0282`). | |||||
| ### Layout | |||||
| Documentation should be written in a FAQ/Wiki style format. | |||||
| Recommended reads: | |||||
| * http://docs.microsoft.com | |||||
| * http://flask.pocoo.org/docs/0.12/ | |||||
| Style consistencies: | |||||
| * Use a ruler set at 70 characters | |||||
| * Links should use long syntax | |||||
| * Pages should be short and concise, not broad and long | |||||
| Example of long link syntax: | |||||
| ``` | |||||
| Please consult the [API Documentation] for more information. | |||||
| [API Documentation]: xref:System.String | |||||
| ``` | |||||
| ### Compiling | |||||
| Documentation is compiled into a static site using [DocFx]. | |||||
| We currently use the most recent build off the dev branch. | |||||
| After making changes, compile your changes into the static site with | |||||
| `docfx`. You can also view your changes live with `docfx --serve`. | |||||
| [DocFx]: https://dotnet.github.io/docfx/ | |||||
| @@ -1,16 +0,0 @@ | |||||
| # Instructions for Building Documentation | |||||
| The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing] | |||||
| 1. Navigate to the root of the repository. | |||||
| 2. (Optional) If you intend to target a specific version, ensure that you | |||||
| have the correct version checked out. | |||||
| 3. Build the library. Run `dotnet build` in the root of this repository. | |||||
| Ensure that the build passes without errors. | |||||
| 4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter | |||||
| to preview the site locally. Some elements of the page may appear incorrect | |||||
| when not hosted by a server. | |||||
| - Remarks: According to the docfx website, this tool does work on Linux under mono. | |||||
| [docfx-main]: https://dotnet.github.io/docfx/ | |||||
| [docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html | |||||
| @@ -1,6 +0,0 @@ | |||||
| ############### | |||||
| # temp file # | |||||
| ############### | |||||
| *.yml | |||||
| .manifest | |||||
| @@ -1,13 +0,0 @@ | |||||
| # API Documentation | |||||
| This is where you will find documentation for all members and objects in Discord.Net | |||||
| __Commonly Used Entities__ | |||||
| * @Discord.WebSocket | |||||
| * @Discord.WebSocket.DiscordSocketClient | |||||
| * @Discord.WebSocket.SocketGuildChannel | |||||
| * @Discord.WebSocket.SocketGuildUser | |||||
| * @Discord.WebSocket.SocketMessage | |||||
| * @Discord.WebSocket.SocketRole | |||||
| @@ -1,74 +0,0 @@ | |||||
| { | |||||
| "metadata": [ | |||||
| { | |||||
| "src": [ | |||||
| { | |||||
| "src": "..", | |||||
| "files": [ | |||||
| "src/**/*.cs" | |||||
| ], | |||||
| "exclude": [ | |||||
| "**/obj/**", | |||||
| "**/bin/**", | |||||
| "_site/**" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "dest": "api", | |||||
| "filter": "filterConfig.yml" | |||||
| } | |||||
| ], | |||||
| "build": { | |||||
| "content": [ | |||||
| { | |||||
| "files": [ | |||||
| "api/**.yml", | |||||
| "api/index.md" | |||||
| ] | |||||
| }, | |||||
| { | |||||
| "files": [ | |||||
| "guides/**.md", | |||||
| "guides/**/toc.yml", | |||||
| "toc.yml", | |||||
| "*.md" | |||||
| ], | |||||
| "exclude": [ | |||||
| "obj/**", | |||||
| "_site/**" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "resource": [ | |||||
| { | |||||
| "files": [ | |||||
| "**/images/**", | |||||
| "**/samples/**" | |||||
| ], | |||||
| "exclude": [ | |||||
| "obj/**", | |||||
| "_site/**" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "overwrite": [ | |||||
| { | |||||
| "files": [ | |||||
| "apidoc/**.md" | |||||
| ], | |||||
| "exclude": [ | |||||
| "obj/**", | |||||
| "_site/**" | |||||
| ] | |||||
| } | |||||
| ], | |||||
| "dest": "_site", | |||||
| "template": [ | |||||
| "default" | |||||
| ], | |||||
| "globalMetadata": { | |||||
| "_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta" | |||||
| }, | |||||
| "noLangKeyword": false | |||||
| } | |||||
| } | |||||
| @@ -1,11 +0,0 @@ | |||||
| apiRules: | |||||
| - exclude: | |||||
| uidRegex: ^Discord\.API$ | |||||
| - exclude: | |||||
| uidRegex: ^Discord\.API.*$ | |||||
| - exclude: | |||||
| uidRegex: ^Discord\.Net\.Converters$ | |||||
| - exclude: | |||||
| uidRegex: ^Discord\.Net.*$ | |||||
| - exclude: | |||||
| uidRegex: ^RegexAnalyzer$ | |||||
| @@ -1,343 +0,0 @@ | |||||
| # The Command Service | |||||
| [Discord.Commands](xref:Discord.Commands) provides an Attribute-based | |||||
| command parser. | |||||
| ## Setup | |||||
| To use Commands, you must create a [Command Service] and a Command | |||||
| Handler. | |||||
| 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` 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 | |||||
| values. | |||||
| [!code-csharp[Command Handler](samples/command_handler.cs)] | |||||
| [Command Service]: xref:Discord.Commands.CommandService | |||||
| [CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig | |||||
| ## With Attributes | |||||
| 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. | |||||
| ### Modules | |||||
| The first step to creating Commands is to create a _module_. | |||||
| 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 is being invoked. | |||||
| **Avoid using long-running code** in your modules wherever possible. | |||||
| 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`. | |||||
| >[!NOTE] | |||||
| >[ModuleBase] is an _abstract_ class, meaning that you may extend it | |||||
| >or override it as you see fit. Your module may inherit from any | |||||
| >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 | |||||
| [Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx | |||||
| [ModuleBase]: xref:Discord.Commands.ModuleBase`1 | |||||
| ### Adding 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. | |||||
| Adding parameters to a Command is done by adding parameters to the | |||||
| parent Task. | |||||
| 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 | |||||
| execute it with `!favoritefood "Key Lime Pie"`. | |||||
| 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. | |||||
| 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 to your Commands, and the Command parser will | |||||
| automatically pick up on it. | |||||
| 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. | |||||
| The `Priority` attributes are sorted in ascending order; the higher | |||||
| priority will be called first. | |||||
| ### Command Context | |||||
| 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, 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 | |||||
| [ReplyAsync]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_ReplyAsync_System_String_System_Boolean_Discord_Embed_Discord_RequestOptions_ | |||||
| ### Example Module | |||||
| At this point, your module should look comparable to this example: | |||||
| [!code-csharp[Example Module](samples/module.cs)] | |||||
| #### Loading Modules Automatically | |||||
| The Command Service can automatically discover all classes in an | |||||
| Assembly that inherit [ModuleBase] and load them. | |||||
| To opt a module out of auto-loading, flag it with | |||||
| [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_ | |||||
| #### 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. | |||||
| [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 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 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 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 one. Typically, | |||||
| submodules are used to create nested groups (although not required to | |||||
| create nested groups). | |||||
| [!code-csharp[Groups and Submodules](samples/groups.cs)] | |||||
| ## With Builders | |||||
| **TODO** | |||||
| ## Dependency Injection | |||||
| 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 one if you wish. | |||||
| Next, add the dependencies that your modules will use to the 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 that you've passed into `LoadAssembly`. | |||||
| Any publicly settable properties will also be filled in the same | |||||
| manner. | |||||
| >[!NOTE] | |||||
| > Annotating a property with a [DontInjectAttribute] attribute will prevent the | |||||
| property from being injected. | |||||
| >[!NOTE] | |||||
| >If you accept `CommandService` or `IServiceProvider` as a parameter | |||||
| in your constructor or as an injectable property, these entries will | |||||
| be filled by the `CommandService` that the Module is loaded from and | |||||
| the `ServiceProvider` that is passed into it respectively. | |||||
| [!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] | |||||
| [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute | |||||
| # Preconditions | |||||
| 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] | |||||
| >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 ship with four bundled Preconditions; you may view their | |||||
| usages on their respective API pages. | |||||
| - @Discord.Commands.RequireContextAttribute | |||||
| - @Discord.Commands.RequireOwnerAttribute | |||||
| - @Discord.Commands.RequireBotPermissionAttribute | |||||
| - @Discord.Commands.RequireUserPermissionAttribute | |||||
| ## Custom Preconditions | |||||
| 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 | |||||
| the [CheckPermissions] method. | |||||
| Your IDE should provide an option to fill this in for you. | |||||
| 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_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_ | |||||
| # Type Readers | |||||
| Type Readers allow you to parse different types of arguments in | |||||
| your commands. | |||||
| By default, the following Types are supported arguments: | |||||
| - bool | |||||
| - char | |||||
| - sbyte/byte | |||||
| - ushort/short | |||||
| - uint/int | |||||
| - ulong/long | |||||
| - float, double, decimal | |||||
| - string | |||||
| - DateTime/DateTimeOffset/TimeSpan | |||||
| - IMessage/IUserMessage | |||||
| - IChannel/IGuildChannel/ITextChannel/IVoiceChannel/IGroupChannel | |||||
| - IUser/IGuildUser/IGroupUser | |||||
| - IRole | |||||
| ### Creating a Type Readers | |||||
| 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 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. | |||||
| 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. | |||||
| [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 | |||||
| [!code-csharp[TypeReaders](samples/typereader.cs)] | |||||
| ### Installing TypeReaders | |||||
| 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_ | |||||
| @@ -1,63 +0,0 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| using System.Reflection; | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| using Discord.Commands; | |||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| public class Program | |||||
| { | |||||
| private CommandService _commands; | |||||
| private DiscordSocketClient _client; | |||||
| private IServiceProvider _services; | |||||
| private static void Main(string[] args) => new Program().StartAsync().GetAwaiter().GetResult(); | |||||
| public async Task StartAsync() | |||||
| { | |||||
| _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() | |||||
| .AddSingleton(_client) | |||||
| .AddSingleton(_commands) | |||||
| .BuildServiceProvider(); | |||||
| await InstallCommandsAsync(); | |||||
| await _client.LoginAsync(TokenType.Bot, token); | |||||
| await _client.StartAsync(); | |||||
| await Task.Delay(-1); | |||||
| } | |||||
| public async Task InstallCommandsAsync() | |||||
| { | |||||
| // Hook the MessageReceived Event into our Command Handler | |||||
| _client.MessageReceived += HandleCommandAsync; | |||||
| // Discover all of the commands in this assembly and load them. | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| } | |||||
| private async Task HandleCommandAsync(SocketMessage messageParam) | |||||
| { | |||||
| // Don't process the command if it was a System Message | |||||
| var message = messageParam as SocketUserMessage; | |||||
| if (message == null) return; | |||||
| // 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; | |||||
| // Create a Command Context | |||||
| 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, _services); | |||||
| if (!result.IsSuccess) | |||||
| await context.Channel.SendMessageAsync(result.ErrorReason); | |||||
| } | |||||
| } | |||||
| @@ -1,18 +0,0 @@ | |||||
| private IServiceProvider _services; | |||||
| private CommandService _commands; | |||||
| public async Task InstallAsync(DiscordSocketClient client) | |||||
| { | |||||
| // Here, we will inject the ServiceProvider with | |||||
| // all of the services our client will use. | |||||
| _services = new ServiceCollection() | |||||
| .AddSingleton(client) | |||||
| .AddSingleton(_commands) | |||||
| // You can pass in an instance of the desired type | |||||
| .AddSingleton(new NotificationService()) | |||||
| // ...or by using the generic method. | |||||
| .AddSingleton<DatabaseService>() | |||||
| .BuildServiceProvider(); | |||||
| // ... | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); | |||||
| } | |||||
| @@ -1,40 +0,0 @@ | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| public class ModuleA : ModuleBase | |||||
| { | |||||
| private readonly DatabaseService _database; | |||||
| // Dependencies can be injected via the constructor | |||||
| public ModuleA(DatabaseService database) | |||||
| { | |||||
| _database = database; | |||||
| } | |||||
| public async Task ReadFromDb() | |||||
| { | |||||
| var x = _database.getX(); | |||||
| await ReplyAsync(x); | |||||
| } | |||||
| } | |||||
| public class ModuleB | |||||
| { | |||||
| // Public settable properties will be injected | |||||
| public AnnounceService { get; set; } | |||||
| // Public properties without setters will not | |||||
| public CommandService Commands { get; } | |||||
| // Public properties annotated with [DontInject] will not | |||||
| [DontInject] | |||||
| public NotificationService { get; set; } | |||||
| public ModuleB(CommandService commands) | |||||
| { | |||||
| Commands = commands; | |||||
| } | |||||
| } | |||||
| @@ -1,6 +0,0 @@ | |||||
| using Discord.Commands; | |||||
| public class InfoModule : ModuleBase<SocketCommandContext> | |||||
| { | |||||
| } | |||||
| @@ -1,18 +0,0 @@ | |||||
| [Group("admin")] | |||||
| public class AdminModule : ModuleBase<SocketCommandContext> | |||||
| { | |||||
| [Group("clean")] | |||||
| public class CleanModule : ModuleBase<SocketCommandContext> | |||||
| { | |||||
| // ~admin clean 15 | |||||
| [Command] | |||||
| public async Task Default(int count = 10) => Messages(count); | |||||
| // ~admin clean messages 15 | |||||
| [Command("messages")] | |||||
| public async Task Messages(int count = 10) { } | |||||
| } | |||||
| // ~admin ban foxbot#0282 | |||||
| [Command("ban")] | |||||
| public async Task Ban(IGuildUser user) { } | |||||
| } | |||||
| @@ -1,41 +0,0 @@ | |||||
| // Create a module with no prefix | |||||
| public class Info : ModuleBase<SocketCommandContext> | |||||
| { | |||||
| // ~say hello -> hello | |||||
| [Command("say")] | |||||
| [Summary("Echoes 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<SocketCommandContext> | |||||
| { | |||||
| // ~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 @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 UserInfoAsync([Summary("The (optional) user to get info for")] SocketUser user = null) | |||||
| { | |||||
| var userInfo = user ?? Context.Client.CurrentUser; | |||||
| await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}"); | |||||
| } | |||||
| } | |||||
| @@ -1,24 +0,0 @@ | |||||
| // (Note: This precondition is obsolete, it is recommended to use the RequireOwnerAttribute that is bundled with Discord.Commands) | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| // Inherit from PreconditionAttribute | |||||
| public class RequireOwnerAttribute : PreconditionAttribute | |||||
| { | |||||
| // Override the CheckPermissions method | |||||
| public async override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
| { | |||||
| // Get the ID of the bot's owner | |||||
| var ownerId = (await services.GetService<DiscordSocketClient>().GetApplicationInfoAsync()).Owner.Id; | |||||
| // If this command was executed by that user, return a success | |||||
| if (context.User.Id == ownerId) | |||||
| return PreconditionResult.FromSuccess(); | |||||
| // Since it wasn't, fail | |||||
| else | |||||
| return PreconditionResult.FromError("You must be the owner of the bot to run this command."); | |||||
| } | |||||
| } | |||||
| @@ -1,15 +0,0 @@ | |||||
| // Note: This example is obsolete, a boolean type reader is bundled with Discord.Commands | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| public class BooleanTypeReader : TypeReader | |||||
| { | |||||
| public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) | |||||
| { | |||||
| bool result; | |||||
| if (bool.TryParse(input, out result)) | |||||
| return Task.FromResult(TypeReaderResult.FromSuccess(result)); | |||||
| return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input could not be parsed as a boolean.")); | |||||
| } | |||||
| } | |||||
| @@ -1,58 +0,0 @@ | |||||
| --- | |||||
| title: Managing Connections | |||||
| --- | |||||
| In Discord.Net, once a client has been started, it will automatically | |||||
| maintain a connection to Discord's gateway, until it is manually | |||||
| stopped. | |||||
| ### Usage | |||||
| To start a connection, invoke the `StartAsync` method on a client that | |||||
| supports a WebSocket connection. | |||||
| These clients include the [DiscordSocketClient] and | |||||
| [DiscordRpcClient], as well as Audio clients. | |||||
| To end a connection, invoke the `StopAsync` method. This will | |||||
| gracefully close any open WebSocket or UdpSocket connections. | |||||
| Since the Start/Stop methods only signal to an underlying connection | |||||
| manager that a connection needs to be started, **they return before a | |||||
| connection is actually made.** | |||||
| As a result, you will need to hook into one of the connection-state | |||||
| based events to have an accurate representation of when a client is | |||||
| ready for use. | |||||
| All clients provide a `Connected` and `Disconnected` event, which is | |||||
| raised respectively when a connection opens or closes. In the case of | |||||
| the DiscordSocketClient, this does **not** mean that the client is | |||||
| ready to be used. | |||||
| A separate event, `Ready`, is provided on DiscordSocketClient, which | |||||
| is raised only when the client has finished guild stream or guild | |||||
| sync, and has a complete guild cache. | |||||
| [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||||
| [DiscordRpcClient]: xref:Discord.Rpc.DiscordRpcClient | |||||
| ### Samples | |||||
| [!code-csharp[Connection Sample](samples/events.cs)] | |||||
| ### Tips | |||||
| Avoid running long-running code on the gateway! If you deadlock the | |||||
| gateway (as explained in [events]), the connection manager will be | |||||
| unable to recover and reconnect. | |||||
| Assuming the client disconnected because of a fault on Discord's end, | |||||
| and not a deadlock on your end, we will always attempt to reconnect | |||||
| and resume a connection. | |||||
| Don't worry about trying to maintain your own connections, the | |||||
| connection manager is designed to be bulletproof and never fail - if | |||||
| your client doesn't manage to reconnect, you've found a bug! | |||||
| [events]: events.md | |||||
| @@ -1,71 +0,0 @@ | |||||
| --- | |||||
| title: Entities | |||||
| --- | |||||
| >[!NOTE] | |||||
| This article is written with the Socket variants of entities in mind, | |||||
| not the general interfaces or Rest/Rpc entities. | |||||
| Discord.Net provides a versatile entity system for navigating the | |||||
| Discord API. | |||||
| ### Inheritance | |||||
| Due to the nature of the Discord API, some entities are designed with | |||||
| 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. | |||||
| For example, in the case of the `MessageReceived` event, a | |||||
| `SocketMessage` is passed in with a channel property of type | |||||
| `SocketMessageChannel`. All messages come from channels capable of | |||||
| messaging, so this is the only variant of a channel that can cover | |||||
| every single case. | |||||
| But that doesn't mean a message _can't_ come from a | |||||
| `SocketTextChannel`, which is a message channel in a guild. To | |||||
| retrieve information about a guild from a message entity, you will | |||||
| need to cast its channel object to a `SocketTextChannel`. | |||||
| ### Navigation | |||||
| All socket entities have navigation properties on them, which allow | |||||
| you to easily navigate to an entity's parent or children. As explained | |||||
| above, you will sometimes need to cast to a more detailed version of | |||||
| an entity to navigate to its parent. | |||||
| ### Accessing Entities | |||||
| The most basic forms of entities, `SocketGuild`, `SocketUser`, and | |||||
| `SocketChannel` can be pulled from the DiscordSocketClient's global | |||||
| cache, and can be retrieved using the respective `GetXXX` method on | |||||
| DiscordSocketClient. | |||||
| >[!TIP] | |||||
| It is **vital** that you use the proper IDs for an entity when using | |||||
| a GetXXX method. It is recommended that you enable Discord's | |||||
| _developer mode_ to allow easy access to entity IDs, found in | |||||
| Settings > Appearance > Advanced | |||||
| More detailed versions of entities can be pulled from the basic | |||||
| entities, e.g. `SocketGuild.GetUser`, which returns a | |||||
| `SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a | |||||
| `SocketGuildChannel`. Again, you may need to cast these objects to get | |||||
| a variant of the type that you need. | |||||
| ### Samples | |||||
| [!code-csharp[Entity Sample](samples/entities.cs)] | |||||
| ### Tips | |||||
| Avoid using boxing-casts to coerce entities into a variant, use the | |||||
| [`as`] keyword, and a null-conditional operator instead. | |||||
| This allows you to write safer code and avoid [InvalidCastExceptions]. | |||||
| 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 | |||||
| @@ -1,84 +0,0 @@ | |||||
| --- | |||||
| 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 | |||||
| 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`. | |||||
| ### Usage | |||||
| To receive data from an event, hook into it using C#'s delegate | |||||
| event pattern. | |||||
| 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; 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 | |||||
| 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 | |||||
| offloaded to another task. | |||||
| This also means that you should not await a task that requests data | |||||
| from Discord's gateway in the same context of an event. Since the | |||||
| gateway will wait on all invoked event handlers to finish before | |||||
| processing any additional data from the gateway, this will create | |||||
| a deadlock that will be impossible to recover from. | |||||
| Exceptions in commands will be swallowed by the gateway and logged out | |||||
| through the client's log method. | |||||
| ### Common Patterns | |||||
| As you may know, events in Discord.Net are only given a signature of | |||||
| `Func<T1, ..., Task>`. There is no room for predefined argument names, | |||||
| so you must either consult IntelliSense, or view the API documentation | |||||
| directly. | |||||
| That being said, there are a variety of common patterns that allow you | |||||
| to infer what the parameters in an event mean. | |||||
| #### Entity, Entity | |||||
| An event handler with a signature of `Func<Entity, Entity, Task>` | |||||
| typically means that the first object will be a clone of the entity | |||||
| _before_ a change was made, and the latter object will be an attached | |||||
| model of the entity _after_ the change was made. | |||||
| This pattern is typically only found on `EntityUpdated` events. | |||||
| #### Cacheable | |||||
| An event handler with a signature of `Func<Cacheable, Entity, Task>` | |||||
| means that the `before` state of the entity was not provided by the | |||||
| API, so it can either be pulled from the client's cache or | |||||
| downloaded from the API. | |||||
| See the documentation for [Cacheable] for more information on this | |||||
| object. | |||||
| [Cacheable]: xref:Discord.Cacheable`2 | |||||
| ### Samples | |||||
| [!code-csharp[Event Sample](samples/events.cs)] | |||||
| ### Tips | |||||
| 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. | |||||
| [DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig | |||||
| @@ -1,47 +0,0 @@ | |||||
| --- | |||||
| title: Logging | |||||
| --- | |||||
| Discord.Net's clients provide a [Log] event that all messages will be | |||||
| disbatched over. | |||||
| For more information about events in Discord.Net, see the [Events] | |||||
| section. | |||||
| [Log]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log | |||||
| [Events]: events.md | |||||
| ### Usage | |||||
| To receive log events, simply hook the discord client's log method | |||||
| 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 | |||||
| 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 | |||||
| in signature to other log events. | |||||
| 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)] | |||||
| #### Tips | |||||
| Due to the nature of Discord.Net's event system, all log event | |||||
| handlers will be executed synchronously on the gateway thread. If your | |||||
| log output will be dumped to a Web API (e.g. Sentry), you are advised | |||||
| to wrap your output in a `Task.Run` so the gateway thread does not | |||||
| become blocked while waiting for logging data to be written. | |||||
| @@ -1,23 +0,0 @@ | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| public class Program | |||||
| { | |||||
| private DiscordSocketClient _client; | |||||
| static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| _client = new DiscordSocketClient(); | |||||
| await _client.LoginAsync(TokenType.Bot, "bot token"); | |||||
| await _client.StartAsync(); | |||||
| Console.WriteLine("Press any key to exit..."); | |||||
| Console.ReadKey(); | |||||
| await _client.StopAsync(); | |||||
| // Wait a little for the client to finish disconnecting before allowing the program to return | |||||
| await Task.Delay(500); | |||||
| } | |||||
| } | |||||
| @@ -1,13 +0,0 @@ | |||||
| public string GetChannelTopic(ulong id) | |||||
| { | |||||
| var channel = client.GetChannel(81384956881809408) as SocketTextChannel; | |||||
| if (channel == null) return ""; | |||||
| return channel.Topic; | |||||
| } | |||||
| public string GuildOwner(SocketChannel channel) | |||||
| { | |||||
| var guild = (channel as SocketGuildChannel)?.Guild; | |||||
| if (guild == null) return ""; | |||||
| return Context.Guild.Owner.Username; | |||||
| } | |||||
| @@ -1,36 +0,0 @@ | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| public class Program | |||||
| { | |||||
| private DiscordSocketClient _client; | |||||
| static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| // When working with events that have Cacheable<IMessage, ulong> parameters, | |||||
| // you must enable the message cache in your config settings if you plan to | |||||
| // use the cached message entity. | |||||
| var _config = new DiscordSocketConfig { MessageCacheSize = 100 }; | |||||
| _client = new DiscordSocketClient(_config); | |||||
| await _client.LoginAsync(TokenType.Bot, "bot token"); | |||||
| await _client.StartAsync(); | |||||
| _client.MessageUpdated += MessageUpdated; | |||||
| _client.Ready += () => | |||||
| { | |||||
| Console.WriteLine("Bot is connected!"); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| await Task.Delay(-1); | |||||
| } | |||||
| private async Task MessageUpdated(Cacheable<IMessage, ulong> before, SocketMessage after, ISocketMessageChannel channel) | |||||
| { | |||||
| // If the message was not in the cache, downloading it will result in getting a copy of `after`. | |||||
| var message = await before.GetOrDownloadAsync(); | |||||
| Console.WriteLine($"{message} -> {after}"); | |||||
| } | |||||
| } | |||||
| @@ -1,29 +0,0 @@ | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| public class Program | |||||
| { | |||||
| private DiscordSocketClient _client; | |||||
| static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| _client = new DiscordSocketClient(new DiscordSocketConfig | |||||
| { | |||||
| LogLevel = LogSeverity.Info | |||||
| }); | |||||
| _client.Log += Log; | |||||
| await _client.LoginAsync(TokenType.Bot, "bot token"); | |||||
| await _client.StartAsync(); | |||||
| await Task.Delay(-1); | |||||
| } | |||||
| private Task Log(LogMessage message) | |||||
| { | |||||
| Console.WriteLine(message.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| } | |||||
| @@ -1,146 +0,0 @@ | |||||
| --- | |||||
| title: Installing Discord.Net | |||||
| --- | |||||
| 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]. | |||||
| 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]. | |||||
| 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]. | |||||
| [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 | |||||
| > [!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! | |||||
| 1. Create a solution for your bot. | |||||
| 2. In Solution Explorer, find the "Dependencies" element under your | |||||
| bot's project. | |||||
| 3. Right click on "Dependencies", and select "Manage NuGet packages." | |||||
|  | |||||
| 4. In the "Browse" tab, search for `Discord.Net`. | |||||
| 5. Install the `Discord.Net` package. | |||||
|  | |||||
| ## Using JetBrains Rider | |||||
| > [!TIP] | |||||
| Make sure to check the "Prerelease" box if installing a dev build! | |||||
| 1. Create a new solution for your bot. | |||||
| 2. Open the NuGet window (Tools > NuGet > Manage NuGet packages for | |||||
| Solution). | |||||
|  | |||||
| 3. In the "Packages" tab, search for `Discord.Net`. | |||||
|  | |||||
| 4. Install by adding the package to your project. | |||||
|  | |||||
| ## Using Visual Studio Code | |||||
| > [!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)] | |||||
| [NuGet.Config file]: #configuring-nuget-without-visual-studio | |||||
| # Compiling from Source | |||||
| In order to compile Discord.Net, you require the following: | |||||
| ### Using Visual Studio | |||||
| - [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. | |||||
| ### Using Command Line | |||||
| - [.NET Core SDK 1.0](https://www.microsoft.com/net/download/core#/sdk) | |||||
| # Additional Information | |||||
| ## 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. | |||||
| 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. | |||||
| Next, you will need to configure your [DiscordSocketClient] to use | |||||
| these custom providers over the default ones. | |||||
| 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)] | |||||
| [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||||
| [DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig | |||||
| ## 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. | |||||
| 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. | |||||
| [!code-xml[NuGet Configuration](samples/nuget.config)] | |||||
| @@ -1,237 +0,0 @@ | |||||
| --- | |||||
| title: Getting Started | |||||
| --- | |||||
| # Making a Ping-Pong bot | |||||
| One of the first steps to getting started with the Discord API is to | |||||
| write a basic ping-pong bot. We will expand on this to create more | |||||
| diverse commands later, but for now, it is a good starting point. | |||||
| ## Creating a Discord Bot | |||||
| 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. | |||||
| 3. Give the application a name (this will be the bot's initial | |||||
| username). | |||||
| 4. Create the Application. | |||||
|  | |||||
| 5. In the application review page, click **Create a Bot User**. | |||||
|  | |||||
| 6. Confirm the popup. | |||||
| 7. If this bot will be public, check "Public Bot." **Do not tick any | |||||
| other options!** | |||||
| [Discord Applications Portal]: https://discordapp.com/developers/applications/me | |||||
| ## Adding your bot to a server | |||||
| 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]. | |||||
| 2. Retrieve the app's **Client ID**. | |||||
|  | |||||
| 3. Create an OAuth2 authorization URL | |||||
| `https://discordapp.com/oauth2/authorize?client_id=<CLIENT ID>&scope=bot` | |||||
| 4. Open the authorization URL in your browser. | |||||
| 5. Select a server. | |||||
| 6. Click on authorize. | |||||
| >[!NOTE] | |||||
| Only servers where you have the `MANAGE_SERVER` permission will be | |||||
| present in this list. | |||||
|  | |||||
| ## Connecting to Discord | |||||
| If you have not already created a project and installed Discord.Net, | |||||
| do that now. (see the [Installing](installing.md) section) | |||||
| ### Async | |||||
| Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] | |||||
| extensively - nearly every operation is asynchronous. | |||||
| 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. | |||||
| To do so, we will be creating an async main in your console | |||||
| application, and rewriting the static main method to invoke the new | |||||
| async main. | |||||
| [!code-csharp[Async Context](samples/intro/async-context.cs)] | |||||
| As a result of this, your program will now start and immediately | |||||
| jump into an async context. This will allow us to create a connection | |||||
| to Discord later on without needing to worry about setting up the | |||||
| correct async implementation. | |||||
| >[!TIP] | |||||
| If your application throws any exceptions within an async context, | |||||
| they will be thrown all the way back up to the first non-async method; | |||||
| since our first non-async method is the program's `Main` method, this | |||||
| 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` | |||||
| parameter. See the [API Documentation] for this event. | |||||
| If you are using your own logging framework, this is where you would | |||||
| invoke it. For the sake of simplicity, we will only be logging to | |||||
| 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 | |||||
| entities. See the [terminology](terminology.md) if you're unsure of | |||||
| the differences. | |||||
| 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 | |||||
| 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. | |||||
| You may create a variable to hold your bot's token (this can be found | |||||
| on your bot's application page on the [Discord Applications Portal]). | |||||
|  | |||||
| >[!IMPORTANT] | |||||
| Your bot's token can be used to gain total access to your bot, so | |||||
| **do __NOT__ share this token with anyone 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 | |||||
| **this method returns as soon as connection logic has been started!** | |||||
| Any methods that rely on the client's state should go in an event | |||||
| handler. | |||||
| 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 | |||||
| the console. | |||||
| The following lines can now be added: | |||||
| [!code-csharp[Create client](samples/intro/client.cs)] | |||||
| At this point, feel free to start your program and see your bot come | |||||
| 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 [installation guide] | |||||
| for how to fix this. | |||||
| [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient | |||||
| [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!". | |||||
| 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, | |||||
| 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 | |||||
| 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 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!". | |||||
| 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 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]. | |||||
| [MessageReceived]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived | |||||
| [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 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. | |||||
| [annotated example]: samples/intro/structure.cs | |||||
| It is important to know that the recommended design pattern of bots | |||||
| 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 | |||||
| @@ -1,15 +0,0 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace MyBot | |||||
| { | |||||
| public class Program | |||||
| { | |||||
| public static void Main(string[] args) | |||||
| => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,17 +0,0 @@ | |||||
| // Program.cs | |||||
| using Discord.WebSocket; | |||||
| // ... | |||||
| private DiscordSocketClient _client; | |||||
| public async Task MainAsync() | |||||
| { | |||||
| _client = new DiscordSocketClient(); | |||||
| _client.Log += Log; | |||||
| string token = "abcdefg..."; // Remember to keep this private! | |||||
| await _client.LoginAsync(TokenType.Bot, token); | |||||
| await _client.StartAsync(); | |||||
| // Block this task until the program is closed. | |||||
| await Task.Delay(-1); | |||||
| } | |||||
| @@ -1,44 +0,0 @@ | |||||
| using Discord; | |||||
| using Discord.WebSocket; | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace MyBot | |||||
| { | |||||
| public class Program | |||||
| { | |||||
| private DiscordSocketClient _client; | |||||
| public static void Main(string[] args) | |||||
| => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| _client = new DiscordSocketClient(); | |||||
| _client.Log += Log; | |||||
| _client.MessageReceived += MessageReceived; | |||||
| string token = "abcdefg..."; // Remember to keep this private! | |||||
| await _client.LoginAsync(TokenType.Bot, token); | |||||
| await _client.StartAsync(); | |||||
| // Block this task until the program is closed. | |||||
| await Task.Delay(-1); | |||||
| } | |||||
| private async Task MessageReceived(SocketMessage message) | |||||
| { | |||||
| if (message.Content == "!ping") | |||||
| { | |||||
| await message.Channel.SendMessageAsync("Pong!"); | |||||
| } | |||||
| } | |||||
| private Task Log(LogMessage msg) | |||||
| { | |||||
| Console.WriteLine(msg.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,22 +0,0 @@ | |||||
| using Discord; | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace MyBot | |||||
| { | |||||
| public class Program | |||||
| { | |||||
| public static void Main(string[] args) | |||||
| => new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| public async Task MainAsync() | |||||
| { | |||||
| } | |||||
| private Task Log(LogMessage msg) | |||||
| { | |||||
| Console.WriteLine(msg.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,14 +0,0 @@ | |||||
| public async Task MainAsync() | |||||
| { | |||||
| // client.Log ... | |||||
| _client.MessageReceived += MessageReceived; | |||||
| // ... | |||||
| } | |||||
| private async Task MessageReceived(SocketMessage message) | |||||
| { | |||||
| if (message.Content == "!ping") | |||||
| { | |||||
| await message.Channel.SendMessageAsync("Pong!"); | |||||
| } | |||||
| } | |||||
| @@ -1,173 +0,0 @@ | |||||
| using System; | |||||
| using System.Reflection; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| using Discord; | |||||
| using Discord.Commands; | |||||
| using Discord.WebSocket; | |||||
| class Program | |||||
| { | |||||
| // Program entry point | |||||
| static void Main(string[] args) | |||||
| { | |||||
| // Call the Program constructor, followed by the | |||||
| // MainAsync method and wait until it finishes (which should be never). | |||||
| new Program().MainAsync().GetAwaiter().GetResult(); | |||||
| } | |||||
| private readonly DiscordSocketClient _client; | |||||
| // Keep the CommandService and DI container around for use with commands. | |||||
| // These two types require you install the Discord.Net.Commands package. | |||||
| private readonly CommandService _commands; | |||||
| private readonly IServiceProvider _services; | |||||
| private Program() | |||||
| { | |||||
| _client = new DiscordSocketClient(new DiscordSocketConfig | |||||
| { | |||||
| // How much logging do you want to see? | |||||
| LogLevel = LogSeverity.Info, | |||||
| // If you or another service needs to do anything with messages | |||||
| // (eg. checking Reactions, checking the content of edited/deleted messages), | |||||
| // you must set the MessageCacheSize. You may adjust the number as needed. | |||||
| //MessageCacheSize = 50, | |||||
| // If your platform doesn't have native websockets, | |||||
| // add Discord.Net.Providers.WS4Net from NuGet, | |||||
| // add the `using` at the top, and uncomment this line: | |||||
| //WebSocketProvider = WS4NetProvider.Instance | |||||
| }); | |||||
| _commands = new CommandService(new CommandServiceConfig | |||||
| { | |||||
| // Again, log level: | |||||
| LogLevel = LogSeverity.Info, | |||||
| // There's a few more properties you can set, | |||||
| // for example, case-insensitive commands. | |||||
| CaseSensitiveCommands = false, | |||||
| }); | |||||
| // Subscribe the logging handler to both the client and the CommandService. | |||||
| _client.Log += Log; | |||||
| _commands.Log += Log; | |||||
| // Setup your DI container. | |||||
| _services = ConfigureServices(), | |||||
| } | |||||
| // If any services require the client, or the CommandService, or something else you keep on hand, | |||||
| // pass them as parameters into this method as needed. | |||||
| // If this method is getting pretty long, you can seperate it out into another file using partials. | |||||
| private static IServiceProvider ConfigureServices() | |||||
| { | |||||
| var map = new ServiceCollection() | |||||
| // Repeat this for all the service classes | |||||
| // and other dependencies that your commands might need. | |||||
| .AddSingleton(new SomeServiceClass()); | |||||
| // When all your required services are in the collection, build the container. | |||||
| // Tip: There's an overload taking in a 'validateScopes' bool to make sure | |||||
| // you haven't made any mistakes in your dependency graph. | |||||
| return map.BuildServiceProvider(); | |||||
| } | |||||
| // Example of a logging handler. This can be re-used by addons | |||||
| // that ask for a Func<LogMessage, Task>. | |||||
| private static Task Log(LogMessage message) | |||||
| { | |||||
| switch (message.Severity) | |||||
| { | |||||
| case LogSeverity.Critical: | |||||
| case LogSeverity.Error: | |||||
| Console.ForegroundColor = ConsoleColor.Red; | |||||
| break; | |||||
| case LogSeverity.Warning: | |||||
| Console.ForegroundColor = ConsoleColor.Yellow; | |||||
| break; | |||||
| case LogSeverity.Info: | |||||
| Console.ForegroundColor = ConsoleColor.White; | |||||
| break; | |||||
| case LogSeverity.Verbose: | |||||
| case LogSeverity.Debug: | |||||
| Console.ForegroundColor = ConsoleColor.DarkGray; | |||||
| break; | |||||
| } | |||||
| 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 | |||||
| // to adjust your project's target framework to 4.6 or higher | |||||
| // (instructions for this are easily Googled). | |||||
| // If you *need* to run on .NET 4.5 for compat/other reasons, | |||||
| // the alternative is to 'return Task.Delay(0);' instead. | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| private async Task MainAsync() | |||||
| { | |||||
| // Centralize the logic for commands into a separate method. | |||||
| await InitCommands(); | |||||
| // Login and connect. | |||||
| await _client.LoginAsync(TokenType.Bot, /* <DON'T HARDCODE YOUR TOKEN> */); | |||||
| await _client.StartAsync(); | |||||
| // Wait infinitely so your bot actually stays connected. | |||||
| await Task.Delay(Timeout.Infinite); | |||||
| } | |||||
| private async Task InitCommands() | |||||
| { | |||||
| // Either search the program and add all Module classes that can be found. | |||||
| // Module classes MUST be marked 'public' or they will be ignored. | |||||
| // You also need to pass your 'IServiceProvider' instance now, | |||||
| // so make sure that's done before you get here. | |||||
| await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | |||||
| // Or add Modules manually if you prefer to be a little more explicit: | |||||
| await _commands.AddModuleAsync<SomeModule>(_services); | |||||
| // 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; | |||||
| } | |||||
| private async Task HandleCommandAsync(SocketMessage arg) | |||||
| { | |||||
| // Bail out if it's a System Message. | |||||
| var msg = arg as SocketUserMessage; | |||||
| if (msg == null) return; | |||||
| // We don't want the bot to respond to itself or other bots. | |||||
| 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 | |||||
| // you want to prefix your commands with. | |||||
| // Uncomment the second half if you also want | |||||
| // commands to be invoked by mentioning the bot instead. | |||||
| if (msg.HasCharPrefix('!', ref pos) /* || msg.HasMentionPrefix(_client.CurrentUser, ref pos) */) | |||||
| { | |||||
| // Create a Command Context. | |||||
| var context = new SocketCommandContext(_client, msg); | |||||
| // 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, pos); | |||||
| // Uncomment the following lines if you want the bot | |||||
| // to send a message if it failed. | |||||
| // This does not catch errors from commands with 'RunMode.Async', | |||||
| // subscribe a handler for '_commands.CommandExecuted' to see those. | |||||
| //if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) | |||||
| // await msg.Channel.SendMessageAsync(result.ErrorReason); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,9 +0,0 @@ | |||||
| using Discord.Providers.WS4Net; | |||||
| using Discord.Providers.UDPClient; | |||||
| using Discord.WebSocket; | |||||
| // ... | |||||
| var client = new DiscordSocketClient(new DiscordSocketConfig | |||||
| { | |||||
| WebSocketProvider = WS4NetProvider.Instance, | |||||
| UdpSocketProvider = UDPClientProvider.Instance, | |||||
| }); | |||||
| @@ -1,6 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <configuration> | |||||
| <packageSources> | |||||
| <add key="discord.net ci feed" value="https://www.myget.org/F/discord-net/api/v3/index.json" /> | |||||
| </packageSources> | |||||
| </configuration> | |||||
| @@ -1,13 +0,0 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <PropertyGroup> | |||||
| <OutputType>Exe</OutputType> | |||||
| <TargetFramework>netcoreapp1.1</TargetFramework> | |||||
| <NoWin32Manifest>true</NoWin32Manifest> | |||||
| </PropertyGroup> | |||||
| <ItemGroup> | |||||
| <PackageReference Include="Discord.Net" Version="1.*" /> | |||||
| </ItemGroup> | |||||
| </Project> | |||||
| @@ -1,40 +0,0 @@ | |||||
| --- | |||||
| uid: Terminology | |||||
| title: Terminology | |||||
| --- | |||||
| # 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. | |||||
| ## 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`. | |||||
| 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 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`). | |||||
| `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`). | |||||
| @@ -1,61 +0,0 @@ | |||||
| # Migrating from 0.9 | |||||
| **1.0.0 is the biggest breaking change the library has gone through, due to massive | |||||
| changes in the design of the library.** | |||||
| >A medium to advanced understanding is recommended when working with this library. | |||||
| It is recommended to familiarize yourself with the entities in 1.0 before continuing. | |||||
| Feel free to look through the library's source directly, look through IntelliSense, or | |||||
| look through our hosted [API Documentation](xref:Discord). | |||||
| ## Entities | |||||
| Most API models function _similarly_ to 0.9, however their names have been changed. | |||||
| You should also keep in mind that we now separate different types of Channels and Users. | |||||
| Before proceeding, please read over @Terminology to understand the naming behind some objects. | |||||
| Below is a table that compares most common 0.9 entities to their 1.0 counterparts. | |||||
| >This should be used mostly for migration purposes. Please take some time to consider whether | |||||
| >or not you are using the right "tool for the job" when working with 1.0 | |||||
| | 0.9 | 1.0 | Notice | | |||||
| | --- | --- | ------ | | |||||
| | Server | @Discord.WebSocket.SocketGuild | | |||||
| | Channel | @Discord.WebSocket.SocketGuildChannel | Applies only to channels that are members of a Guild | | |||||
| | Channel.IsPrivate | @Discord.WebSocket.SocketDMChannel | |||||
| | ChannelType.Text | @Discord.WebSocket.SocketTextChannel | This applies only to Text Channels in Guilds | |||||
| | ChannelType.Voice | @Discord.WebSocket.SocketVoiceChannel | This applies only to Voice Channels in Guilds | |||||
| | User | @Discord.WebSocket.SocketGuildUser | This applies only to users belonging to a Guild* | |||||
| | Profile | @Discord.WebSocket.SocketGuildUser | |||||
| | Message | @Discord.WebSocket.SocketUserMessage | |||||
| \* To retrieve an @Discord.WebSocket.SocketGuildUser, you must retrieve the user from an @Discord.WebSocket.SocketGuild. | |||||
| ## Event Registration | |||||
| Prior to 1.0, events were registered using the standard c# `Handler(EventArgs)` pattern. In 1.0, | |||||
| events are delegates, but are still registered the same. | |||||
| For example, let's look at [DiscordSocketClient.MessageReceived](xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_MessageReceived) | |||||
| To hook an event into MessageReceived, we now use the following code: | |||||
| [!code-csharp[Event Registration](samples/event.cs)] | |||||
| > **All Event Handlers in 1.0 MUST return Task!** | |||||
| If your event handler is marked as `async`, it will automatically return `Task`. However, | |||||
| if you do not need to execute asynchronus code, do _not_ mark your handler as `async`, and instead, | |||||
| stick a `return Task.CompletedTask` at the bottom. | |||||
| [!code-csharp[Sync Event Registration](samples/sync_event.cs)] | |||||
| **Event handlers no longer require a sender.** The only arguments your event handler needs to accept | |||||
| are the parameters used by the event. It is recommended to look at the event in IntelliSense or on the | |||||
| API docs before implementing it. | |||||
| ## Async | |||||
| Nearly everything in 1.0 is an async Task. You should always await any tasks you invoke. | |||||
| @@ -1,4 +0,0 @@ | |||||
| _client.MessageReceived += async (msg) => | |||||
| { | |||||
| await msg.Channel.SendMessageAsync(msg.Content); | |||||
| } | |||||
| @@ -1,5 +0,0 @@ | |||||
| _client.Log += (msg) => | |||||
| { | |||||
| Console.WriteLine(msg.ToString()); | |||||
| return Task.CompletedTask; | |||||
| } | |||||
| @@ -1,27 +0,0 @@ | |||||
| - name: Getting Started | |||||
| items: | |||||
| - name: Installation | |||||
| href: getting_started/installing.md | |||||
| - name: Your First Bot | |||||
| href: getting_started/intro.md | |||||
| - name: Terminology | |||||
| href: getting_started/terminology.md | |||||
| - name: Basic Concepts | |||||
| items: | |||||
| - name: Logging Data | |||||
| href: concepts/logging.md | |||||
| - name: Working with Events | |||||
| href: concepts/events.md | |||||
| - name: Managing Connections | |||||
| href: concepts/connections.md | |||||
| - name: Entities | |||||
| href: concepts/entities.md | |||||
| - name: The Command Service | |||||
| items: | |||||
| - name: Command Guide | |||||
| href: commands/commands.md | |||||
| - name: Voice | |||||
| items: | |||||
| - name: Voice Guide | |||||
| href: voice/sending-voice.md | |||||
| - name: Migrating from 0.9 | |||||
| @@ -1,10 +0,0 @@ | |||||
| private Process CreateStream(string path) | |||||
| { | |||||
| return Process.Start(new ProcessStartInfo | |||||
| { | |||||
| FileName = "ffmpeg", | |||||
| Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1", | |||||
| UseShellExecute = false, | |||||
| RedirectStandardOutput = true, | |||||
| }); | |||||
| } | |||||
| @@ -1,11 +0,0 @@ | |||||
| private async Task SendAsync(IAudioClient client, string path) | |||||
| { | |||||
| // Create FFmpeg using the previous example | |||||
| using (var ffmpeg = CreateStream(path)) | |||||
| using (var output = ffmpeg.StandardOutput.BaseStream) | |||||
| using (var discord = client.CreatePCMStream(AudioApplication.Mixed)) | |||||
| { | |||||
| try { await output.CopyToAsync(discord); } | |||||
| finally { await discord.FlushAsync(); } | |||||
| } | |||||
| } | |||||
| @@ -1,10 +0,0 @@ | |||||
| [Command("join")] | |||||
| public async Task JoinChannel(IVoiceChannel channel = null) | |||||
| { | |||||
| // Get the audio channel | |||||
| channel = channel ?? (msg.Author as IGuildUser)?.VoiceChannel; | |||||
| if (channel == null) { await msg.Channel.SendMessageAsync("User must be in a voice channel, or a voice channel must be passed as an argument."); return; } | |||||
| // For the next step with transmitting audio, you would want to pass this Audio Client in to a service. | |||||
| var audioClient = await channel.ConnectAsync(); | |||||
| } | |||||
| @@ -1,101 +0,0 @@ | |||||
| --- | |||||
| title: Sending Voice | |||||
| --- | |||||
| **Information on this page is subject to change!** | |||||
| >[!WARNING] | |||||
| >This article is out of date, and has not been rewritten yet. | |||||
| Information is not guaranteed to be accurate. | |||||
| ## Installing | |||||
| Audio requires two native libraries, `libsodium` and `opus`. | |||||
| Both of these libraries must be placed in the runtime directory of your | |||||
| bot. (When developing on .NET Framework, this would be `bin/debug`, | |||||
| when developing on .NET Core, this is where you execute `dotnet run` | |||||
| from; typically the same directory as your csproj). | |||||
| For Windows Users, precompiled binaries are available for your | |||||
| convienence [here](https://discord.foxbot.me/binaries/). | |||||
| For Linux Users, you will need to compile [Sodium] and [Opus] from | |||||
| source, or install them from your package manager. | |||||
| [Sodium]: https://download.libsodium.org/libsodium/releases/ | |||||
| [Opus]: http://downloads.xiph.org/releases/opus/ | |||||
| ## Joining a Channel | |||||
| 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.IAudioChannel. | |||||
| [!code-csharp[Joining a Channel](samples/joining_audio.cs)] | |||||
| The client will sustain a connection to this channel until it is | |||||
| kicked, disconnected from Discord, or told to disconnect. | |||||
| It should be noted that voice connections are created on a per-guild | |||||
| basis; only one audio connection may be open by the bot in a single | |||||
| guild. To switch channels within a guild, invoke [ConnectAsync] on | |||||
| another voice channel in the guild. | |||||
| [IAudioClient]: xref:Discord.Audio.IAudioClient | |||||
| [ConnectAsync]: xref:Discord.IAudioChannel#Discord_IAudioChannel_ConnectAsync_Action_IAudioClient__ | |||||
| ## Transmitting Audio | |||||
| ### With FFmpeg | |||||
| [FFmpeg] is an open source, highly versatile AV-muxing tool. This is | |||||
| the recommended method of transmitting audio. | |||||
| Before you begin, you will need to have a version of FFmpeg downloaded | |||||
| and placed somewhere in your PATH (or alongside the bot, in the same | |||||
| location as libsodium and opus). Windows binaries are available on | |||||
| [FFmpeg's download page]. | |||||
| [FFmpeg]: https://ffmpeg.org/ | |||||
| [FFmpeg's download page]: https://ffmpeg.org/download.html | |||||
| First, you will need to create a Process that starts FFmpeg. An | |||||
| example of how to do this is included below, though it is important | |||||
| that you return PCM at 48000hz. | |||||
| >[!NOTE] | |||||
| >As of the time of this writing, Discord.Audio struggles significantly | |||||
| >with processing audio that is already opus-encoded; you will need to | |||||
| >use the PCM write streams. | |||||
| [!code-csharp[Creating FFmpeg](samples/audio_create_ffmpeg.cs)] | |||||
| Next, to transmit audio from FFmpeg to Discord, you will need to | |||||
| pull an [AudioOutStream] from your [IAudioClient]. Since we're using | |||||
| PCM audio, use [IAudioClient.CreatePCMStream]. | |||||
| The sample rate argument doesn't particularly matter, so long as it is | |||||
| a valid rate (120, 240, 480, 960, 1920, or 2880). For the sake of | |||||
| simplicity, I recommend using 1920. | |||||
| 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_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 | |||||
| for the majority of cases, you can just use [Stream.CopyToAsync], as | |||||
| shown below. | |||||
| [Stream.CopyToAsync]: https://msdn.microsoft.com/en-us/library/hh159084(v=vs.110).aspx | |||||
| If you are implementing a queue for sending songs, it's likely that | |||||
| you will want to wait for audio to stop playing before continuing on | |||||
| to the next song. You can await `AudioOutStream.FlushAsync` to wait for | |||||
| the audio client's internal buffer to clear out. | |||||
| [!code-csharp[Sending Audio](samples/audio_ffmpeg.cs)] | |||||
| @@ -1,13 +0,0 @@ | |||||
| # Discord.Net Documentation | |||||
| Discord.Net is an asynchronous, multiplatform .NET Library used to interface with the [Discord API](https://discordapp.com/). | |||||
| If this is your first time using Discord.Net, you should refer to the [Intro](guides/getting_started/intro.md) for tutorials. | |||||
| More experienced users might refer to the [API Documentation](api/index.md) for a breakdown of the individuals objects in the library. | |||||
| For additional resources: | |||||
| - [Discord API Guild](https://discord.gg/discord-api) - Look for `#dotnet_discord-net` | |||||
| - [GitHub](https://github.com/RogueException/Discord.Net/tree/dev) | |||||
| - [NuGet](https://www.nuget.org/packages/Discord.Net/) | |||||
| - [MyGet Feed](https://www.myget.org/feed/Packages/discord-net) - Addons and nightly builds | |||||
| @@ -1,6 +0,0 @@ | |||||
| - name: Guides | |||||
| href: guides/ | |||||
| - name: API Documentation | |||||
| href: api/ | |||||
| homepage: api/index.md | |||||