diff --git a/docs/guides/images/intro-add-bot.png b/docs/guides/images/intro-add-bot.png new file mode 100644 index 000000000..e40997ed3 Binary files /dev/null and b/docs/guides/images/intro-add-bot.png differ diff --git a/docs/guides/images/intro-client-id.png b/docs/guides/images/intro-client-id.png new file mode 100644 index 000000000..e370aa2ec Binary files /dev/null and b/docs/guides/images/intro-client-id.png differ diff --git a/docs/guides/images/intro-create-app.png b/docs/guides/images/intro-create-app.png new file mode 100644 index 000000000..7aceb84b4 Binary files /dev/null and b/docs/guides/images/intro-create-app.png differ diff --git a/docs/guides/images/intro-create-bot.png b/docs/guides/images/intro-create-bot.png new file mode 100644 index 000000000..0522358cf Binary files /dev/null and b/docs/guides/images/intro-create-bot.png differ diff --git a/docs/guides/images/intro-token.png b/docs/guides/images/intro-token.png new file mode 100644 index 000000000..8617cb76f Binary files /dev/null and b/docs/guides/images/intro-token.png differ diff --git a/docs/guides/installing.md b/docs/guides/installing.md index 37a8b3d45..077d46213 100644 --- a/docs/guides/installing.md +++ b/docs/guides/installing.md @@ -11,7 +11,7 @@ Optionally, you may compile from source and install yourself. Currently, Discord.Net targets [.NET Standard] 1.3, and offers support for .NET Standard 1.1. If your application will be targeting .NET Standard 1.1, -please see the [additional steps](#installing-on-.net-standard-1.1). +please see the [additional steps](#installing-on-.net-standard-11). Since Discord.Net is built on the .NET Standard, it is also recommended to create applications using [.NET Core], though you are not required to. When @@ -46,11 +46,13 @@ project 3. Right click on 'Dependencies', and select 'Manage NuGet packages' ![Step 3](images/install-vs-deps.png) 4. In the 'browse' tab, search for 'Discord.Net' + > [!TIP] -> Don't forget to change your package source if you're installing from the -> developer feed. -> Also make sure to check 'Enable Prereleases' if installing a dev build! +Don't forget to change your package source if you're installing from the +developer feed. +Also make sure to check 'Enable Prereleases' if installing a dev build! 5. Install the 'Discord.Net' package + ![Step 5](images/install-vs-nuget.png) ## Using JetBrains Rider diff --git a/docs/guides/intro.md b/docs/guides/intro.md index f16bc9883..314f2c32e 100644 --- a/docs/guides/intro.md +++ b/docs/guides/intro.md @@ -2,49 +2,223 @@ title: Getting Started --- -# Getting Started +# Making a Ping-Pong bot -## Requirements +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. -Discord.Net supports logging in with all variations of Discord Accounts, however the Discord API reccomends using a `Bot Account`. +## Creating a Discord Bot -You may [register a bot account here](https://discordapp.com/developers/applications/me). +Before you can begin writing your bot, it is necessary to create a bot +account on Discord. -Bot accounts must be added to a server, you must use the [OAuth 2 Flow](https://discordapp.com/developers/docs/topics/oauth2#adding-bots-to-guilds) to add them to servers. +1. Visit the [Discord Applications Portal] +2. Create a New Application +3. Give the application a name (this will be the bot's initial +username). +4. Create the Application +![Step 4](images/intro-create-app.png) +5. In the application review page, click **Create a Bot User** +![Step 5](images/intro-create-bot.png) +6. Confirm the popup +7. If this bot will be public, check 'Public Bot'. +**Do not tick any other options!** -## Installation +[Discord Applications Portal]: https://discordapp.com/developers/applications/me -You can install Discord.Net 1.0 from our [MyGet Feed](https://www.myget.org/feed/Packages/discord-net). +## Adding your bot to a server -**For most users writing bots, install only `Discord.Net.WebSocket`.** +Bots **can not** use invite links, they must be explicitly invited +through the OAuth2 flow. -You may add the MyGet feed to Visual Studio directly from `https://www.myget.org/F/discord-net/api/v3/index.json`. +1. Open your bot's application on the [Discord Applications Portal] +2. Retrieve the app's **Client ID**. +![Step 2](images/intro-client-id.png) +3. Create an OAuth2 authorization URL +`https://discordapp.com/oauth2/authorize?client_id=&scope=bot` +4. Open the authorization URL in your browser +5. Select a server -You can also pull the latest source from [GitHub](https://github.com/RogueException/Discord.Net). +>[!NOTE] +Only servers where you have the `MANAGE_SERVER` permission will be +present in this list. + +6. Click authorize +![Step 6](images/intro-add-bot.png) + +## 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 be 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 immidiately +jump into an async context. This will allow us later on to create a +connection to Discord, without needing to worry about setting up the +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. ->[!WARNING] ->The versions of Discord.Net on NuGet are behind the versions this ->documentation is written for. ->You MUST install from MyGet or Source! +### Creating a logging method -## Async +Before we create and configure a Discord client, we will add a method +to handle Discord.Net's log events. -Discord.Net uses C# tasks extensiely - nearly all operations return -one. +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. -It is highly reccomended these tasks be awaited whenever possible. -To do so requires the calling method to be marked as async, which -can be problematic in a console application. An example of how to -get around this is provided below. +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. -For more information, go to [MSDN's Async-Await section.](https://msdn.microsoft.com/en-us/library/hh191443.aspx) +[!code-csharp[Async Context](samples/intro/logging.cs)] -## First Steps +### Creating a Discord Client -[!code-csharp[Main](samples/first-steps.cs)] +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]). +![Token](images/intro-token.png) + +>[!IMPORTANT] +Your bot's token can be used to gain total access to your bot, so +**do __NOT__ share this token with anyone!**. It may behoove you to +store this token in an external file if you plan on distributing the +source code for your bot. + +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. >[!NOTE] ->In previous versions of Discord.Net, you had to hook into the `Ready` and `GuildAvailable` events to determine when your client was ready for use. ->In 1.0, the [ConnectAsync] method will automatically wait for the Ready event, and for all guilds to stream. To avoid this, pass `false` into `ConnectAsync`. +Connection logic is incomplete as of the current build. Events will +soon be added to indicate when the client's state is ready for use; +(rewrite this section when possible) + +Finally, we will want to block the async main method from returning +until after the application is exited. To do this, we can await an +infinite delay, or any other blocking method, such as reading from +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 [installing guide] +for how to fix this. + +[TAP]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/async +[API Documentation]: xref:Discord.Rest.BaseDiscordClient#Discord_Rest_BaseDiscordClient_Log +[DiscordSocketClient]: xref:Discord.WebSocket.DiscordSocketClient +[installing guide]: installing.md#installing-on-.net-standard-11 + +### Handling a 'ping' + +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 in to +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 came - `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 bot that will be carrying out multiple +commands, it is strongly encouraged 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.SocketMessageChannel +[completed program]: samples/intro/complete.cs + +# Building a bot with commands + +This section will show you how to write a program that is ready for +[commands](commands.md). Note that this 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). -[ConnectAsync]: xref:Discord.WebSocket.DiscordSocketClient#Discord_WebSocket_DiscordSocketClient_ConnectAsync_System_Boolean_ \ No newline at end of file +**todo:** diagram of bot structure \ No newline at end of file diff --git a/docs/guides/samples/intro/async-context.cs b/docs/guides/samples/intro/async-context.cs new file mode 100644 index 000000000..c01ddec55 --- /dev/null +++ b/docs/guides/samples/intro/async-context.cs @@ -0,0 +1,15 @@ +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/samples/intro/client.cs b/docs/guides/samples/intro/client.cs new file mode 100644 index 000000000..ea7c91932 --- /dev/null +++ b/docs/guides/samples/intro/client.cs @@ -0,0 +1,16 @@ +// Program.cs +using Discord.WebSocket; +// ... +public async Task MainAsync() +{ + var 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); +} \ No newline at end of file diff --git a/docs/guides/samples/intro/complete.cs b/docs/guides/samples/intro/complete.cs new file mode 100644 index 000000000..b59b6b4d9 --- /dev/null +++ b/docs/guides/samples/intro/complete.cs @@ -0,0 +1,42 @@ +using Discord; +using Discord.WebSocket; +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() + { + var 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; + } + } +} \ No newline at end of file diff --git a/docs/guides/samples/intro/logging.cs b/docs/guides/samples/intro/logging.cs new file mode 100644 index 000000000..4fb85a063 --- /dev/null +++ b/docs/guides/samples/intro/logging.cs @@ -0,0 +1,22 @@ +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/samples/intro/message.cs b/docs/guides/samples/intro/message.cs new file mode 100644 index 000000000..d3cda46e5 --- /dev/null +++ b/docs/guides/samples/intro/message.cs @@ -0,0 +1,14 @@ +public async Task MainAsync() +{ + // client.Log ... + client.MessageReceived += MessageReceived; + // ... +} + +private async Task MessageReceived(SocketMessage message) +{ + if (message.Content == "!ping") + { + await message.Channel.SendMessageAsync("Pong!"); + } +} \ No newline at end of file diff --git a/docs/guides/samples/first-steps.cs b/docs/guides/samples/intro/structure.cs similarity index 96% rename from docs/guides/samples/first-steps.cs rename to docs/guides/samples/intro/structure.cs index 95aacc9d3..01dff7bc6 100644 --- a/docs/guides/samples/first-steps.cs +++ b/docs/guides/samples/intro/structure.cs @@ -77,7 +77,6 @@ class Program // Login and connect. await _client.LoginAsync(TokenType.Bot, /* */); - // Prior to rc-00608 this was ConnectAsync(); await _client.StartAsync(); // Wait infinitely so your bot actually stays connected. @@ -96,10 +95,10 @@ class Program await _commands.AddModuleAsync(); // Subscribe a handler to see if a message invokes a command. - _client.MessageReceived += CmdHandler; + _client.MessageReceived += HandleCommandAsync; } - private async Task CmdHandler(SocketMessage arg) + private async Task HandleCommandAsync(SocketMessage arg) { // Bail out if it's a System Message. var msg = arg as SocketUserMessage;