From 84c42f5e287901ff7fe9be34439287e59ab2c98a Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Wed, 11 Apr 2018 00:02:28 +0800 Subject: [PATCH 1/3] Split command service guide into various articles + Each section has gone through a thorough proof-reading and cleanup + This should help with readability. --- .../Commands/DontInjectAttribute.Overwrite.md | 3 +- docs/faq/commands/Commands.md | 2 +- docs/guides/commands/dependency-injection.md | 8 +- .../guides/commands/{commands.md => intro.md} | 122 ++---------------- docs/guides/commands/preconditions.md | 53 ++++++++ .../commands/samples/dependency_module.cs | 34 ++--- docs/guides/commands/samples/empty-module.cs | 2 + .../commands/samples/typereader-register.cs | 29 +++++ docs/guides/commands/typereaders.md | 69 ++++++++++ docs/guides/toc.yml | 6 +- 10 files changed, 186 insertions(+), 142 deletions(-) rename docs/guides/commands/{commands.md => intro.md} (64%) create mode 100644 docs/guides/commands/preconditions.md create mode 100644 docs/guides/commands/samples/typereader-register.cs create mode 100644 docs/guides/commands/typereaders.md diff --git a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md index e395f2058..950d2990c 100644 --- a/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md +++ b/docs/_overwrites/Commands/DontInjectAttribute.Overwrite.md @@ -6,8 +6,7 @@ remarks: *content The attribute can be applied to a public settable property inside a @Discord.Commands.ModuleBase based class. By applying this attribute, the marked property will not be automatically injected of the -dependency. See [Dependency Injection](xref:Guides.Commands.Intro#dependency-injection) -to learn more. +dependency. See @Guides.Commands.DI to learn more. --- uid: Discord.Commands.DontInjectAttribute diff --git a/docs/faq/commands/Commands.md b/docs/faq/commands/Commands.md index 7d0bdf8bc..e54daf0ac 100644 --- a/docs/faq/commands/Commands.md +++ b/docs/faq/commands/Commands.md @@ -57,7 +57,7 @@ persist throughout execution. Think of it like a chest that holds whatever you throw at it that won't be affected by anything unless you want it to. Note that you should also learn Microsoft's implementation of [Dependency Injection] \([video]) before proceeding, as well -as how it works in [Discord.Net](xref:Guides.Commands.Intro#usage-in-modules). +as how it works in [Discord.Net](xref:Guides.Commands.DI#usage-in-modules). A brief example of service and dependency injection can be seen below. diff --git a/docs/guides/commands/dependency-injection.md b/docs/guides/commands/dependency-injection.md index fc99cd6e2..db63e00c4 100644 --- a/docs/guides/commands/dependency-injection.md +++ b/docs/guides/commands/dependency-injection.md @@ -16,6 +16,8 @@ DI when writing your modules. to use in the modules. 3. Pass the service collection into `AddModulesAsync`. +### Example - Setting up Injection + [!code-csharp[IServiceProvider Setup](samples/dependency_map_setup.cs)] ## Usage in Modules @@ -34,8 +36,10 @@ manner. > 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. +> the `IServiceProvider` that is passed into it respectively. + +### Example - Injection in Modules -[!code-csharp[ServiceProvider in Modules](samples/dependency_module.cs)] +[!code-csharp[IServiceProvider in Modules](samples/dependency_module.cs)] [DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute \ No newline at end of file diff --git a/docs/guides/commands/commands.md b/docs/guides/commands/intro.md similarity index 64% rename from docs/guides/commands/commands.md rename to docs/guides/commands/intro.md index 322fa61cd..8036ed09e 100644 --- a/docs/guides/commands/commands.md +++ b/docs/guides/commands/intro.md @@ -99,19 +99,20 @@ For example: * ...etc. Starting from 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](#type-readers). +a full list of types that are parsed by default can +be found in @Guides.Commands.TypeReaders. [CommandAttribute]: xref:Discord.Commands.CommandAttribute #### Optional Parameters Parameters, by default, are always required. To make a parameter -optional, give it a default value (i.e. `int num = 0`). To accept a comma-separated list, -set the parameter to `params Type[]`. +optional, give it a default value (i.e. `int num = 0`). #### Parameters with Spaces +To accept a comma-separated list, set the parameter to `params Type[]`. + Should a parameter include spaces, the parameter **must** be wrapped in quotes. For example, for a command with a parameter `string food`, you would execute it with @@ -198,7 +199,8 @@ that are placed in the Module's constructor must be injected into an ### Module Properties Modules with `public` settable properties will have the dependencies -injected after the construction of the Module. +injected after the construction of the module. See @Guides.Commands.DI +to learn more. ### Module Groups @@ -216,112 +218,4 @@ 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)] - -# 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 -* @Discord.Commands.RequireNsfwAttribute - -## 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 [CheckPermissionsAsync] 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)] - -[CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync* -[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess* -[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError* - -# 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` -* `Nullable` where applicible -* Any implementation of `IChannel`/`IMessage`/`IUser`/`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 [ReadAsync] 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.FromSuccess* -[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult.FromError* -[ReadAsync]: xref:Discord.Commands.TypeReader.ReadAsync* - -## Registering TypeReaders - -TypeReaders are not automatically discovered by the Command Service -and must be explicitly added. - -To register a TypeReader, invoke [CommandService.AddTypeReader]. - -> [!WARNING] -> TypeReaders must be added prior to module discovery, otherwise your -> TypeReaders may not work! - -[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService.AddTypeReader* - -### Sample - -[!code-csharp[TypeReaders](samples/typereader.cs)] \ No newline at end of file +[!code-csharp[Groups and Submodules](samples/groups.cs)] \ No newline at end of file diff --git a/docs/guides/commands/preconditions.md b/docs/guides/commands/preconditions.md new file mode 100644 index 000000000..f3b79f505 --- /dev/null +++ b/docs/guides/commands/preconditions.md @@ -0,0 +1,53 @@ +--- +uid: Guides.Commands.Preconditions +title: Preconditions +--- + +# 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. + +There are two types of Preconditions you can use: + +* [PreconditionAttribute] can be applied to Modules, Groups, or Commands. +* [ParameterPreconditionAttribute] can be applied to Parameters. + +You may visit their respective API documentation to find out more. + +[PreconditionAttribute]: xref:Discord.Commands.PreconditionAttribute +[ParameterPreconditionAttribute]: xref:Discord.Commands.ParameterPreconditionAttribute + +## Bundled Preconditions + +@Discord.Commands ship with several bundled Preconditions; you may +view their usages on their respective API pages. + +* @Discord.Commands.RequireContextAttribute +* @Discord.Commands.RequireOwnerAttribute +* @Discord.Commands.RequireBotPermissionAttribute +* @Discord.Commands.RequireUserPermissionAttribute +* @Discord.Commands.RequireNsfwAttribute + +## 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 [CheckPermissionsAsync] 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)] + +[CheckPermissionsAsync]: xref:Discord.Commands.PreconditionAttribute.CheckPermissionsAsync* +[PreconditionResult.FromSuccess]: xref:Discord.Commands.PreconditionResult.FromSuccess* +[PreconditionResult.FromError]: xref:Discord.Commands.PreconditionResult.FromError* diff --git a/docs/guides/commands/samples/dependency_module.cs b/docs/guides/commands/samples/dependency_module.cs index 2014ed881..3c9b51d7d 100644 --- a/docs/guides/commands/samples/dependency_module.cs +++ b/docs/guides/commands/samples/dependency_module.cs @@ -1,40 +1,30 @@ -using Discord; -using Discord.Commands; -using Discord.WebSocket; - -public class ModuleA : ModuleBase +public class DatabaseModule : ModuleBase { private readonly DatabaseService _database; // Dependencies can be injected via the constructor - public ModuleA(DatabaseService database) + public DatabaseModule(DatabaseService database) { _database = database; } - public async Task ReadFromDb() + [Command("read")] + public async Task ReadFromDbAsync() { - var x = _database.getX(); - await ReplyAsync(x); + await ReplyAsync(_database.GetData()); } } -public class ModuleB : ModuleBase +public class MixModule : ModuleBase { - // Public settable properties will be injected. - public AnnounceService Announce { get; set; } + // Public settable properties will be injected + public AnnounceService AnnounceService { get; set; } - // Public properties without setters will not be injected. - public CommandService Commands { get; } + // Public properties without setters will not be injected + public ImageService ImageService { get; } // Public properties annotated with [DontInject] will not - // be injected. + // be injected [DontInject] public NotificationService NotificationService { get; set; } - - public ModuleB(CommandService commands) - { - Commands = commands; - } - -} +} \ 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 6483c7cd2..db62032c2 100644 --- a/docs/guides/commands/samples/empty-module.cs +++ b/docs/guides/commands/samples/empty-module.cs @@ -1,5 +1,7 @@ using Discord.Commands; +// Keep in mind your module **must** be public and inherit ModuleBase. +// If it isn't, it will not be discovered by AddModulesAsync! public class InfoModule : ModuleBase { diff --git a/docs/guides/commands/samples/typereader-register.cs b/docs/guides/commands/samples/typereader-register.cs new file mode 100644 index 000000000..292caea6f --- /dev/null +++ b/docs/guides/commands/samples/typereader-register.cs @@ -0,0 +1,29 @@ +public class CommandHandler +{ + private readonly CommandService _commands; + private readonly DiscordSocketClient _client; + private readonly IServiceProvider _services; + + public CommandHandler(CommandService commands, DiscordSocketClient client, IServiceProvider services) + { + _commands = commands; + _client = client; + _services = services; + } + + public async Task SetupAsync() + { + _client.MessageReceived += CommandHandleAsync; + + // Add BooleanTypeReader to type read for the type "bool" + _commands.AddTypeReader(typeof(bool), new BooleanTypeReader()); + + // Then register the modules + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } + + public async Task CommandHandleAsync(SocketMessage msg) + { + // ... + } +} \ No newline at end of file diff --git a/docs/guides/commands/typereaders.md b/docs/guides/commands/typereaders.md new file mode 100644 index 000000000..a502fb3c8 --- /dev/null +++ b/docs/guides/commands/typereaders.md @@ -0,0 +1,69 @@ +--- +uid: Guides.Commands.TypeReaders +title: Type Readers +--- + +# 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` +* `Nullable` where applicible +* Any implementation of `IChannel`/`IMessage`/`IUser`/`IRole` + +## Creating a Type Reader + +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 [ReadAsync] method. + +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. + +> [!NOTE] +> Visual Studio can help you implement missing members +> from the abstract class by using the "Implement Abstract Class" +> IntelliSense hint. + +[TypeReaderResult]: xref:Discord.Commands.TypeReaderResult +[TypeReaderResult.FromSuccess]: xref:Discord.Commands.TypeReaderResult.FromSuccess* +[TypeReaderResult.FromError]: xref:Discord.Commands.TypeReaderResult.FromError* +[ReadAsync]: xref:Discord.Commands.TypeReader.ReadAsync* + +### Example - Creating a Type Reader + +[!code-csharp[TypeReaders](samples/typereader.cs)] + +## Registering a Type Reader + +TypeReaders are not automatically discovered by the Command Service +and must be explicitly added. + +To register a TypeReader, invoke [CommandService.AddTypeReader]. + +> [!IMPORTANT] +> TypeReaders must be added prior to module discovery, otherwise your +> TypeReaders may not work! + +[CommandService.AddTypeReader]: xref:Discord.Commands.CommandService.AddTypeReader* + +### Example - Adding a Type Reader + +[!code-csharp[Adding TypeReaders](samples/typereader-register.cs)] \ No newline at end of file diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index 9bf259da5..51755b91f 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -20,8 +20,12 @@ topicUid: Guides.Concepts.Entities - name: The Command Service items: - - name: Introduction to Command Service + - name: Introduction topicUid: Guides.Commands.Intro + - name: TypeReaders + topicUid: Guides.Commands.TypeReaders + - name: Preconditions + topicUid: Guides.Commands.Preconditions - name: Dependency Injection topicUid: Guides.Commands.DI - name: Post-execution Handling From 4bb6263abde871a386a1c74b64e760d1fb8e1f52 Mon Sep 17 00:00:00 2001 From: Hsu Still <341464@gmail.com> Date: Wed, 11 Apr 2018 21:33:09 +0800 Subject: [PATCH 2/3] Improve various articles + Rename Guides.Concepts.Logging title from 'Logging' to 'Logging Events/Data'. + Rewrite various sections of Guides.GettingStarted.FirstBot and Guides.GettingStarted.Installing based on suggested changes from #1027. + Rename project.csproj example to project.xml to avoid auto-compilation from Visual Studio Code. + Rename 'samples\intro' to 'samples\first-bot' to reflect on the article name change. + Clean first-bot samples. --- docs/guides/concepts/logging.md | 2 +- docs/guides/getting_started/first-bot.md | 153 ++++++++++-------- docs/guides/getting_started/installing.md | 70 ++++---- .../samples/first-bot/async-context.cs | 9 ++ .../samples/{intro => first-bot}/client.cs | 10 +- .../samples/first-bot/complete.cs | 38 +++++ .../samples/first-bot/logging.cs | 5 + .../samples/{intro => first-bot}/message.cs | 2 +- .../samples/{intro => first-bot}/structure.cs | 0 .../samples/intro/async-context.cs | 15 -- .../getting_started/samples/intro/complete.cs | 44 ----- .../getting_started/samples/intro/logging.cs | 22 --- .../samples/{project.csproj => project.xml} | 1 + docs/guides/introduction/intro.md | 2 +- 14 files changed, 182 insertions(+), 191 deletions(-) create mode 100644 docs/guides/getting_started/samples/first-bot/async-context.cs rename docs/guides/getting_started/samples/{intro => first-bot}/client.cs (71%) create mode 100644 docs/guides/getting_started/samples/first-bot/complete.cs create mode 100644 docs/guides/getting_started/samples/first-bot/logging.cs rename docs/guides/getting_started/samples/{intro => first-bot}/message.cs (92%) rename docs/guides/getting_started/samples/{intro => first-bot}/structure.cs (100%) delete mode 100644 docs/guides/getting_started/samples/intro/async-context.cs delete mode 100644 docs/guides/getting_started/samples/intro/complete.cs delete mode 100644 docs/guides/getting_started/samples/intro/logging.cs rename docs/guides/getting_started/samples/{project.csproj => project.xml} (99%) diff --git a/docs/guides/concepts/logging.md b/docs/guides/concepts/logging.md index f556e6ba6..dba78006f 100644 --- a/docs/guides/concepts/logging.md +++ b/docs/guides/concepts/logging.md @@ -1,6 +1,6 @@ --- uid: Guides.Concepts.Logging -title: Logging +title: Logging Events/Data --- # Logging in Discord.Net diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md index 87ddaf6b8..e928b368f 100644 --- a/docs/guides/getting_started/first-bot.md +++ b/docs/guides/getting_started/first-bot.md @@ -5,14 +5,15 @@ title: Start making a bot # 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. +One of ways to get started with the Discord API is to write a basic +ping-pong bot. This bot will respond to a simple command "ping." +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. +Before writing your bot, it is necessary to create a bot account via the +Discord Applications Portal first. 1. Visit the [Discord Applications Portal]. 2. Create a New Application. @@ -37,12 +38,14 @@ 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**. +2. Retrieve the application's **Client ID**. ![Step 2](images/intro-client-id.png) 3. Create an OAuth2 authorization URL - `https://discordapp.com/oauth2/authorize?client_id=&scope=bot` + + - `https://discordapp.com/oauth2/authorize?client_id=&scope=bot` + 4. Open the authorization URL in your browser. 5. Select a server. 6. Click on authorize. @@ -56,26 +59,26 @@ through the OAuth2 flow. ## Connecting to Discord If you have not already created a project and installed Discord.Net, -do that now. (see the [Installing](xref:Guides.GettingStarted.Installation) section) +do that now. + +For more information, see @Guides.GettingStarted.Installation. ### Async Discord.Net uses .NET's [Task-based Asynchronous Pattern (TAP)] -extensively - nearly every operation is asynchronous. +extensively - nearly every operation is asynchronous. It is highly +recommended that these operations are awaited in a +properly established async context whenever possible. -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 establish an async context, we will be creating an async main method +in your console application, and rewriting the static main method to +invoke the new async main. -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)] +[!code-csharp[Async Context](samples/first-bot/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 +to Discord later on without having to worry about setting up the correct async implementation. > [!TIP] @@ -83,9 +86,11 @@ correct async implementation. > 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. +> 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 @@ -100,57 +105,61 @@ 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. +the console. -[!code-csharp[Async Context](samples/intro/logging.cs)] +You may learn more about this concept in @Guides.Concepts.Logging. + +[!code-csharp[Async Context](samples/first-bot/logging.cs)] [API Documentation]: xref: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](xref:Guides.GettingStarted.Terminology) if you're unsure of -the differences. +Finally, we can create a new connection to Discord. + +Since we are writing a bot, we will be using a [DiscordSocketClient] +along with socket entities. See @Guides.GettingStarted.Terminology +if you are 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 +To establish a new connection, we will create an instance of +[DiscordSocketClient] in the new async main. You may pass in an +optional @Discord.WebSocket.DiscordSocketConfig 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. +log handler that we had just created. Events in Discord.Net work +similarly to any other events in C#. -Next, you will need to "login to Discord" with the `LoginAsync` -method. +Next, you will need to "login to Discord" with the [LoginAsync] +method with the application's "token." -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]). +> [!NOTE] +> Please note that this is not the same as the application's +> "client secret." ![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 else!** It may behoove you -> to store this token in an external file if you plan on distributing +> to store this token in an external source if you plan on distributing > the source code for your bot. -We may now invoke the client's `StartAsync` method, which will +We may now invoke the client's [StartAsync] method, which will start connection/reconnection logic. It is important to note that -**this method returns as soon as connection logic has been started!** +**this method will return as soon as connection logic has been started!** Any methods that rely on the client's state should go in an event -handler. +handler. This means that you should **not** directly be interacting with +the client before it is fully ready. 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. +when running the application. 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)] +[!code-csharp[Create client](samples/first-bot/client.cs)] At this point, feel free to start your program and see your bot come online in Discord. @@ -162,6 +171,8 @@ online in Discord. > for how to fix this. [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient +[LoginAsync]: xref:Discord.Rest.BaseDiscordClient.LoginAsync* +[StartAsync]: xref:Discord.WebSocket.DiscordSocketClient.StartAsync* [installation guide]: xref:Guides.GettingStarted.Installation#installing-on-net-standard-11 ### Handling a 'ping' @@ -169,18 +180,18 @@ online in Discord. > [!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. +> in the [Command Guide](xref:Guides.Commands.Intro) 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!". +Now that we have learned to open a connection to Discord, we can +begin handling messages that the users are sending. To start out, our +bot will listen for any message whose content is equal to `!ping` and +will 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 +`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`. @@ -189,45 +200,49 @@ 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 +Inside the branch of this condition, we will want to send a message, +`Pong!`, back to the channel from which the message comes from. 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!". +channel object is of type [ISocketMessageChannel], we can invoke the +[SendMessageAsync] instance method. For the message content, send back +a string, "Pong!". You should have now added the following lines, -[!code-csharp[Message](samples/intro/message.cs)] +[!code-csharp[Message](samples/first-bot/message.cs)] -Now your first bot is complete. You may continue to add on to this +Now that 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]. +> [!NOTE] +> For your reference, you may view the [completed program]. [MessageReceived]: xref:Discord.WebSocket.BaseSocketClient.MessageReceived [SocketMessage]: xref:Discord.WebSocket.SocketMessage [SocketMessageChannel]: xref:Discord.WebSocket.ISocketMessageChannel -[completed program]: samples/intro/complete.cs -[Command Guide]: xref:Guides.Commands.Intro +[SendMessageAsync]: xref:Discord.WebSocket.ISocketMessageChannel.SendMessageAsync* +[completed program]: samples/first-bot/complete.cs # Building a bot with commands -This section will show you how to write a program that is ready for -[Commands](xref:Guides.Commands.Intro). Note that we will not be -explaining _how_ to write Commands or Services, it will only be -covering the general structure. +@Guides.Commands.Intro will guide you through how to setup a program +that is ready for [CommandService], a service that is ready for +advanced command usage. For reference, view an [annotated example] of this structure. -[annotated example]: samples/intro/structure.cs +[annotated example]: samples/first-bot/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). \ No newline at end of file +should be to separate... + +1. the program (initialization and command handler) +2. the modules (handle commands) +3. the services (persistent storage, pure functions, data manipulation) + +[CommandService]: xref:Discord.Commands.CommandService \ No newline at end of file diff --git a/docs/guides/getting_started/installing.md b/docs/guides/getting_started/installing.md index 2a802776f..9ee6d90b2 100644 --- a/docs/guides/getting_started/installing.md +++ b/docs/guides/getting_started/installing.md @@ -3,19 +3,22 @@ uid: Guides.GettingStarted.Installation 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 Installation -Optionally, you may compile from source and install yourself. +Discord.Net is distributed through the NuGet package manager, so it is +recommended for you to install the library that way. -# Supported Platforms +Alternatively, you may compile from the source and install the library +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 +Since Discord.Net is built on top of .NET Standard, it is also +recommended to create applications using [.NET Core], although it is not required. When using .NET Framework, it is suggested to target `.NET Framework 4.6.1` or higher. @@ -23,13 +26,13 @@ required. When using .NET Framework, it is suggested to target [.NET Core]: https://docs.microsoft.com/en-us/dotnet/articles/core/ [additional steps]: #installing-on-net-standard-11 -# Installing with NuGet +## Installing with NuGet Release builds of Discord.Net will be published to the [official NuGet feed]. -Development builds of Discord.Net, as well as add-ons are published to -our development [MyGet feed]. +Development builds of Discord.Net, as well as add-ons, will be +published to our [MyGet feed]. Direct feed link: `https://www.myget.org/F/discord-net/api/v3/index.json` @@ -41,7 +44,7 @@ Not sure how to add a direct feed? See how [with Visual Studio] or [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](#tab/vs-install) +### [Using Visual Studio](#tab/vs-install) > [!TIP] > Don't forget to change your package source if you're installing from @@ -49,8 +52,8 @@ Not sure how to add a direct feed? See how [with Visual Studio] or > 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 +1. Create a new solution for your bot. +2. In the 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) @@ -58,7 +61,7 @@ Not sure how to add a direct feed? See how [with Visual Studio] or 5. Install the `Discord.Net` package. ![Step 5](images/install-vs-nuget.png) -## [Using JetBrains Rider](#tab/rider-install) +### [Using JetBrains Rider](#tab/rider-install) > [!TIP] > Make sure to check the "Prerelease" box if installing a dev build! @@ -72,7 +75,7 @@ Not sure how to add a direct feed? See how [with Visual Studio] or 4. Install by adding the package to your project. ![Step 4](images/install-rider-add.png) -## [Using Visual Studio Code](#tab/vs-code) +### [Using Visual Studio Code](#tab/vs-code) > [!TIP] > Don't forget to add the package source to a [NuGet.Config file] if @@ -81,11 +84,11 @@ Not sure how to add a direct feed? See how [with Visual Studio] or 1. Create a new project for your bot. 2. Add `Discord.Net` to your .csproj. -[!code[Sample .csproj](samples/project.csproj)] +[!code[Sample .csproj](samples/project.xml)] [NuGet.Config file]: #configuring-nuget-without-visual-studio -## [Using dotnet CLI](#tab/dotnet-cli) +### [Using dotnet CLI](#tab/dotnet-cli) > [!TIP] > Don't forget to add the package source to a [NuGet.Config file] if @@ -98,11 +101,11 @@ Not sure how to add a direct feed? See how [with Visual Studio] or *** -# Compiling from Source +## Compiling from Source -In order to compile Discord.Net, you require the following: +In order to compile Discord.Net, you will need the following: -## Using Visual Studio +### Using Visual Studio - [Visual Studio 2017](https://www.visualstudio.com/) - [.NET Core SDK] @@ -110,31 +113,32 @@ In order to compile Discord.Net, you require the following: The .NET Core and Docker (Preview) workload is required during Visual Studio installation. -## Using Command Line +### Using Command Line - [.NET Core SDK] [.NET Core SDK]: https://www.microsoft.com/net/download/ -# Additional Information +## Additional Information -## Installing on .NET Standard 1.1 +### Installing on .NET Standard 1.1 For applications targeting a runtime corresponding with .NET Standard -1.1 or 1.2, the builtin WebSocket and UDP provider will not work. For -applications which utilize a WebSocket connection to Discord -(WebSocket or RPC), third-party provider packages will need to be +1.1 or 1.2, the built-in WebSocket and UDP provider will not work. For +applications which utilize a WebSocket connection to Discord, such as +WebSocket or RPC, third-party provider packages will need to be installed and configured. -First, install the following packages through NuGet, or compile +> [!NOTE] +> `Discord.Net.Providers.UDPClient` is _only_ required if your +> bot will be utilizing voice chat. + +First, install the following packages through NuGet, or compile them 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. @@ -147,16 +151,16 @@ are passing into your client. [DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient [DiscordSocketConfig]: xref:Discord.WebSocket.DiscordSocketConfig -## Configuring NuGet without Visual Studio +### Configuring NuGet without Visual Studio If you plan on deploying your bot or developing outside of Visual Studio, you will need to create a local NuGet configuration file for your project. -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 is located. Paste the following snippets into this configuration file, adding any -additional feeds as necessary. +additional feeds if necessary. [!code[NuGet Configuration](samples/nuget.config)] diff --git a/docs/guides/getting_started/samples/first-bot/async-context.cs b/docs/guides/getting_started/samples/first-bot/async-context.cs new file mode 100644 index 000000000..3c98c9e46 --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/async-context.cs @@ -0,0 +1,9 @@ +public class Program +{ + public static void Main(string[] args) + => new Program().MainAsync().GetAwaiter().GetResult(); + + public async Task MainAsync() + { + } +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/client.cs b/docs/guides/getting_started/samples/first-bot/client.cs similarity index 71% rename from docs/guides/getting_started/samples/intro/client.cs rename to docs/guides/getting_started/samples/first-bot/client.cs index a73082052..b3bf39865 100644 --- a/docs/guides/getting_started/samples/intro/client.cs +++ b/docs/guides/getting_started/samples/first-bot/client.cs @@ -1,17 +1,17 @@ -// 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! + // Remember to keep this private or to read this + // from an external source! + string token = "abcdefg..."; await _client.LoginAsync(TokenType.Bot, token); await _client.StartAsync(); // Block this task until the program is closed. await Task.Delay(-1); -} +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/complete.cs b/docs/guides/getting_started/samples/first-bot/complete.cs new file mode 100644 index 000000000..965b57a5e --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/complete.cs @@ -0,0 +1,38 @@ +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 += MessageReceivedAsync; + + // Remember to keep this private or to read this + // from an external source! + string token = "abcdefg..."; + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); + + // Block this task until the program is closed. + await Task.Delay(-1); + } + + private async Task MessageReceivedAsync(SocketMessage message) + { + if (message.Content == "!ping") + { + await message.Channel.SendMessageAsync("Pong!"); + } + } + + private Task Log(LogMessage msg) + { + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/first-bot/logging.cs b/docs/guides/getting_started/samples/first-bot/logging.cs new file mode 100644 index 000000000..c6ffc406e --- /dev/null +++ b/docs/guides/getting_started/samples/first-bot/logging.cs @@ -0,0 +1,5 @@ +private Task Log(LogMessage msg) +{ + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; +} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/message.cs b/docs/guides/getting_started/samples/first-bot/message.cs similarity index 92% rename from docs/guides/getting_started/samples/intro/message.cs rename to docs/guides/getting_started/samples/first-bot/message.cs index d6fd90778..f636d6f35 100644 --- a/docs/guides/getting_started/samples/intro/message.cs +++ b/docs/guides/getting_started/samples/first-bot/message.cs @@ -1,6 +1,6 @@ public async Task MainAsync() { - // client.Log ... + // ... _client.MessageReceived += MessageReceived; // ... } diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/first-bot/structure.cs similarity index 100% rename from docs/guides/getting_started/samples/intro/structure.cs rename to docs/guides/getting_started/samples/first-bot/structure.cs diff --git a/docs/guides/getting_started/samples/intro/async-context.cs b/docs/guides/getting_started/samples/intro/async-context.cs deleted file mode 100644 index c01ddec55..000000000 --- a/docs/guides/getting_started/samples/intro/async-context.cs +++ /dev/null @@ -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() - { - } - } -} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/intro/complete.cs b/docs/guides/getting_started/samples/intro/complete.cs deleted file mode 100644 index 23b59ce6f..000000000 --- a/docs/guides/getting_started/samples/intro/complete.cs +++ /dev/null @@ -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; - } - } -} diff --git a/docs/guides/getting_started/samples/intro/logging.cs b/docs/guides/getting_started/samples/intro/logging.cs deleted file mode 100644 index 4fb85a063..000000000 --- a/docs/guides/getting_started/samples/intro/logging.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/docs/guides/getting_started/samples/project.csproj b/docs/guides/getting_started/samples/project.xml similarity index 99% rename from docs/guides/getting_started/samples/project.csproj rename to docs/guides/getting_started/samples/project.xml index d4b485afc..983ac228b 100644 --- a/docs/guides/getting_started/samples/project.csproj +++ b/docs/guides/getting_started/samples/project.xml @@ -1,4 +1,5 @@ +