diff --git a/docs/api/index.md b/docs/api/index.md index a16321045..57fa48203 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,3 +1,3 @@ # API Reference -Welcome to DSharpPlus API reference. To begin, select a namespace, then a class, from the table of contents on the left. \ No newline at end of file +Welcome to DisCatSharp API reference. To begin, select a namespace, then a class, from the table of contents on the left. \ No newline at end of file diff --git a/docs/articles/advanced_topics/buttons.md b/docs/articles/advanced_topics/buttons.md index e693d231a..d493448e4 100644 --- a/docs/articles/advanced_topics/buttons.md +++ b/docs/articles/advanced_topics/buttons.md @@ -1,170 +1,170 @@ --- uid: advanced_topics_buttons title: Buttons --- # Introduction Buttons are a feature in Discord based on the interaction framework appended to the bottom of a message which come in several colors. You will want to familarize yourself with the [message builder](xref:beyond_basics_messagebuilder) as it and similar builder objects will be used throughout this article. With buttons, you can have up to five buttons in a row, and up to five (5) rows of buttons, for a maximum for 25 buttons per message. Furthermore, buttons come in two types: regular, and link. Link buttons contain a Url field, and are always grey. # Buttons Continued > [!WARNING] > Component (Button) Ids on buttons should be unique, as this is what's sent back when a user presses a button. > > Link buttons do **not** have a custom id and do **not** send interactions when pressed. Buttons consist of five parts: - Id - Style - Label - Emoji - Disabled The id of the button is a settable string on buttons, and is specified by the developer. Discord sends this id back in the [interaction object](https://discord.dev/interactions/slash-commands#interaction). Non-link buttons come in four colors, which are known as styles: Blurple, Grey, Green, and Red. Or as their styles are named: Primary, Secondary, Success, and Danger respectively. How does one construct a button? It's simple, buttons support constructor and object initialization like so: ```cs var myButton = new DiscordButtonComponent() { CustomId = "my_very_cool_button", Style = ButtonStyle.Primary, Label = "Very cool button!", Emoji = new DiscordComponentEmoji("😀") }; ``` This will create a blurple button with the text that reads "Very cool button!". When a user pushes it, `"my_very_cool_button"` will be sent back as the `Id` property on the event. This is expanded on in the [how to respond to buttons](#responding-to-button-presses). The label of a button is optional *if* an emoji is specified. The label can be up to 80 characters in length. The emoji of a button is a [partial emoji object](https://discord.dev/interactions/message-components#component-object), which means that **any valid emoji is usable**, even if your bot does not have access to it's origin server. The disabled field of a button is rather self explanatory. If this is set to true, the user will see a greyed out button which they cannot interact with. # Adding buttons > [!NOTE] > This article will use underscores in button ids for consistency and styling, but spaces are also usable. Adding buttons to a message is relatively simple. Simply make a builder, and sprinkle some content and the buttons you'd like. ```cs var builder = new DiscordMessageBuilder(); builder.WithContent("This message has buttons! Pretty neat innit?"); ``` Well, there's a builder, but no buttons. What now? Simply make a new button object (`DiscordButtonComponent`) and call `.AddComponents()` on the MessageBuilder. ```cs var myButton = new DiscordButtonComponent { CustomId = "my_custom_id", Label = "This is a button!", Style = ButtonStyle.Primary, }; var builder = new DiscordMessageBuilder() .WithContent("This message has buttons! Pretty neat innit?") .AddComponents(myButton); ``` Now you have a message with a button. Congratulations! It's important to note that `.AddComponents()` will create a new row with each call, so **add everything you want on one row in one call!** Buttons can be added in any order you fancy. Lets add 5 to demonstrate each color, and a link button for good measure. ```cs var builder = new DiscordMessageBuilder() .WithContent("This message has buttons! Pretty neat innit?") .AddComponents(new DiscordComponent[] { new DiscordButtonComponent(ButtonStyle.Primary, "1_top", "Blurple!"), new DiscordButtonComponent(ButtonStyle.Secondary, "2_top", "Grey!"), new DiscordButtonComponent(ButtonStyle.Success, "3_top", "Green!"), new DiscordButtonComponent(ButtonStyle.Danger, "4_top", "Red!"), new DiscordLinkButtonComponent("https://some-super-cool.site", "Link!") }); ``` As promised, not too complicated. Links however are `DiscordLinkButtonComponent`, which takes a URL as it's first parameter, and the label. Link buttons can also have an emoji, like regular buttons. Lets also add a second row of buttons, but disable them, so the user can't push them all willy-nilly. ```cs builder.AddComponents(new DiscordComponent[] { new DiscordButtonComponent(ButtonStyle.Primary, "1_top_d", "Blurple!", true), new DiscordButtonComponent(ButtonStyle.Secondary, "2_top_d", "Grey!", true), new DiscordButtonComponent(ButtonStyle.Success, "3_top_d", "Green!", true), new DiscordButtonComponent(ButtonStyle.Danger, "4_top_d", "Red!", true), new DiscordLinkButtonComponent("https://some-super-cool.site", "Link!", true) }); ``` Practically identical, but now with `true` as an extra paremeter. This is the `Disabled` property. Produces a message like such: ![Buttons](/images/advanced_topics_buttons_01.png) Well, that's all neat, but lets say you want to add an emoji. Being able to use any emoji is pretty neat, afterall. That's also very simple! ```cs var myButton = new DiscordButtonComponent ( ButtonStyle.Primary, "emoji_button", null, false, new DiscordComponentEmoji(595381687026843651) ); ``` And you're done! Simply add that to a builder, and when you send, you'll get a message that has a button with a little Pikachu enjoying a lolipop. Adorable. ![PikaLolipop](/images/advanced_topics_buttons_02.png) # Responding to button presses -When any button is pressed, it will fire the [ComponentInteractionCreated](xref:DSharpPlusNextGen.DiscordClient#DSharpPlusNextGen_DiscordClient_ComponentInteractionCreated) event. +When any button is pressed, it will fire the [ComponentInteractionCreated](xref:DisCatSharp.DiscordClient#DisCatSharp_DiscordClient_ComponentInteractionCreated) event. In the event args, `Id` will be the id of the button you specified. There's also an `Interaction` property, which contains the interaction the event created. It's important to respond to an interaction within 3 seconds, or it will time out. Responding after this period will throw a `NotFoundException`. With buttons, there are two new response types: `DefferedMessageUpdate` and `UpdateMessage`. -Using `DeferredMessageUpdate` lets you create followup messages via the [followup message builder](xref:DSharpPlusNextGen.Entities.DiscordFollowupMessageBuilder). The button will return to being in it's 'dormant' state, or it's 'unpushed' state, if you will. +Using `DeferredMessageUpdate` lets you create followup messages via the [followup message builder](xref:DisCatSharp.Entities.DiscordFollowupMessageBuilder). The button will return to being in it's 'dormant' state, or it's 'unpushed' state, if you will. You have 15 minutes from that point to make followup messages. Responding to that interaction looks like this: ```cs client.ComponentInteractionCreated += async (s, e) => { await e.Interaction.CreateResponseAsync(InteractionResponseType.DefferedMessageUpdate); // Do things.. // } ``` If you would like to update the message when a button is pressed, however, you'd use `UpdateMessage` instead, and pass a `DiscordInteractionResponseBuilder` with the new content you'd like. ```cs client.ComponentInteractionCreated += async (s, e) => { await e.Interaction.CreateResponseAsync(InteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().WithContent("No more buttons for you >:)")); } ``` This will update the message, and without the infamous (edited) next to it. Nice. # Interactivity Along with the typical `WaitForMessageAsync` and `WaitForReactionAsync` methods provided by interactivity, there are also button implementations as well. More information about how interactivity works can be found in [the interactivity article](xref:interactivity) Since buttons create interactions, there are also two additional properties in the configuration: - RepsonseBehavior - ResponseMessage ResponseBehavior is what interactivity will do when handling something that isn't a valid valid button, in the context of waiting for a specific button. It defaults to `Ignore`, which will cause the interaction fail. Alternatively, setting it to `Ack` will acknowledge the button, and continue waiting. Respond will reply with an ephemeral message with the aforementioned response message. ResponseBehavior only applies to the overload accepting a string id of the button to wait for. diff --git a/docs/articles/audio/lavalink/configuration.md b/docs/articles/audio/lavalink/configuration.md index 25460d0a8..23edcbe60 100644 --- a/docs/articles/audio/lavalink/configuration.md +++ b/docs/articles/audio/lavalink/configuration.md @@ -1,112 +1,112 @@ --- uid: audio_lavalink_configuration title: Lavalink Configuration --- # Setting up DSharpPlus.Lavalink ## Configuring Your Client -To begin using DSharpPlusNextGen's Lavalink client, you will need to add the `DSharpPlusNextGen.Lavalink` nuget package. Once installed, simply add these namespaces at the top of your bot file: +To begin using DisCatSharp's Lavalink client, you will need to add the `DisCatSharp.Lavalink` nuget package. Once installed, simply add these namespaces at the top of your bot file: ```csharp -using DSharpPlusNextGen.Net; -using DSharpPlusNextGen.Lavalink; +using DisCatSharp.Net; +using DisCatSharp.Lavalink; ``` After that, we will need to create a configuration for our extension to use. This is where the special values from the server configuration are used. ```csharp var endpoint = new ConnectionEndpoint { Hostname = "127.0.0.1", // From your server configuration. Port = 2333 // From your server configuration }; var lavalinkConfig = new LavalinkConfiguration { Password = "youshallnotpass", // From your server configuration. RestEndpoint = endpoint, SocketEndpoint = endpoint }; ``` Finally, initialize the extension. ```csharp var lavalink = Discord.UseLavalink(); ``` ## Connecting with Lavalink We are now ready to connect to the server. Call the Lavalink extension's connect method and pass the configuration. Make sure to call this **after** your Discord client connects. This can be called either directly after your client's connect method or in your client's ready event. ```csharp LavalinkNode = await Lavalink.ConnectAsync(lavalinkConfig); ``` Your main bot file should now look like this: ```csharp using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using DSharpPlusNextGen; -using DSharpPlusNextGen.Net; -using DSharpPlusNextGen.Lavalink; +using DisCatSharp; +using DisCatSharp.Net; +using DisCatSharp.Lavalink; namespace MyFirstMusicBot { class Program { public static DiscordClient Discord; static void Main(string[] args) { MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult(); } static async Task MainAsync(string[] args) { Discord = new DiscordClient(new DiscordConfiguration { Token = "", TokenType = TokenType.Bot, MinimumLogLevel = LogLevel.Debug }); var endpoint = new ConnectionEndpoint { Hostname = "127.0.0.1", // From your server configuration. Port = 2333 // From your server configuration }; var lavalinkConfig = new LavalinkConfiguration { Password = "youshallnotpass", // From your server configuration. RestEndpoint = endpoint, SocketEndpoint = endpoint }; var lavalink = Discord.UseLavalink(); await Discord.ConnectAsync(); await lavalink.ConnectAsync(lavalinkConfig); // Make sure this is after Discord.ConnectAsync(). await Task.Delay(-1); } } } ``` -We are now ready to start the bot. If everything is configured properly, you should see a Lavalink connection appear in your DSharpPlusNextGen console: +We are now ready to start the bot. If everything is configured properly, you should see a Lavalink connection appear in your DisCatSharp console: ``` [2020-10-10 17:56:07 -04:00] [403 /LavalinkConn] [Debug] Connection to Lavalink node established ``` And a client connection appear in your Lavalink console: ``` INFO 5180 --- [ XNIO-1 task-1] io.undertow.servlet : Initializing Spring DispatcherServlet 'dispatcherServlet' INFO 5180 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' INFO 5180 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms INFO 5180 --- [ XNIO-1 task-1] l.server.io.HandshakeInterceptorImpl : Incoming connection from /0:0:0:0:0:0:0:1:58238 INFO 5180 --- [ XNIO-1 task-1] lavalink.server.io.SocketServer : Connection successfully established from /0:0:0:0:0:0:0:1:58238 ``` We are now ready to set up some music commands! diff --git a/docs/articles/audio/lavalink/music_commands.md b/docs/articles/audio/lavalink/music_commands.md index 9135f1052..a855bea45 100644 --- a/docs/articles/audio/lavalink/music_commands.md +++ b/docs/articles/audio/lavalink/music_commands.md @@ -1,338 +1,338 @@ --- uid: audio_lavalink_music_commands title: Lavalink Music Commands --- # Adding Music Commands This article assumes that you know how to use CommandsNext. If you do not, you should learn [here](xref:commands_intro) before continuing with this guide. ## Prerequisites Before we start we will need to make sure CommandsNext is configured. For this we can make a simple configuration and command class: ```csharp -using DSharpPlusNextGen.CommandsNext; +using DisCatSharp.CommandsNext; namespace MyFirstMusicBot { public class MyLavalinkCommands : BaseCommandModule { } } ``` And be sure to register it in your program file: ```csharp CommandsNext = Discord.UseCommandsNext(new CommandsNextConfiguration { StringPrefixes = new string[] { ";;" } }); CommandsNext.RegisterCommands(); ``` ## Adding join and leave commands Your bot, and Lavalink, will need to connect to a voice channel to play music. Let's create the base for these commands: ```csharp [Command] public async Task Join(CommandContext ctx, DiscordChannel channel) { } [Command] public async Task Leave(CommandContext ctx, DiscordChannel channel) { } ``` In order to connect to a voice channel, we'll need to do a few things. 1. Get our node connection. You can either use linq or `GetIdealNodeConnection()` 2. Check if the channel is a voice channel, and tell the user if not. 3. Connect the node to the channel. And for the leave command: 1. Get the node connection, using the same process. 2. Check if the channel is a voice channel, and tell the user if not. 3. Get our existing connection. 4. Check if the connection exists, and tell the user if not. 5. Disconnect from the channel. `GetIdealNodeConnection()` will return the least affected node through load balancing, which is useful for larger bots. It can also filter nodes based on an optional voice region to use the closest nodes available. Since we only have one connection we can use linq's `.First()` method on the extensions connected nodes to get what we need. So far, your command class should look something like this: ```csharp using System.Threading.Tasks; -using DSharpPlusNextGen; -using DSharpPlusNextGen.Entities; -using DSharpPlusNextGen.CommandsNext; -using DSharpPlusNextGen.CommandsNext.Attributes; +using DisCatSharp; +using DisCatSharp.Entities; +using DisCatSharp.CommandsNext; +using DisCatSharp.CommandsNext.Attributes; namespace MyFirstMusicBot { public class MyLavalinkCommands : BaseCommandModule { [Command] public async Task Join(CommandContext ctx, DiscordChannel channel) { var lava = ctx.Client.GetLavalink(); if (!lava.ConnectedNodes.Any()) { await ctx.RespondAsync("The Lavalink connection is not established"); return; } var node = lava.ConnectedNodes.Values.First(); if (channel.Type != ChannelType.Voice) { await ctx.RespondAsync("Not a valid voice channel."); return; } await node.ConnectAsync(channel); await ctx.RespondAsync($"Joined {channel.Name}!"); } [Command] public async Task Leave(CommandContext ctx, DiscordChannel channel) { var lava = ctx.Client.GetLavalink(); if (!lava.ConnectedNodes.Any()) { await ctx.RespondAsync("The Lavalink connection is not established"); return; } var node = lava.ConnectedNodes.Values.First(); if (channel.Type != ChannelType.Voice) { await ctx.RespondAsync("Not a valid voice channel."); return; } var conn = node.GetGuildConnection(channel.Guild); if (conn == null) { await ctx.RespondAsync("Lavalink is not connected."); return; } await conn.DisconnectAsync(); await ctx.RespondAsync($"Left {channel.Name}!"); } } } ``` ## Adding player commands Now that we can join a voice channel, we can make our bot play music! Let's now create the base for a play command: ```csharp [Command] public async Task Play(CommandContext ctx, [RemainingText] string search) { } ``` One of Lavalink's best features is its ability to search for tracks from a variety of media sources, such as YouTube, SoundCloud, Twitch, and more. This is what makes bots like Rythm, Fredboat, and Groovy popular. The search is used in a REST request to get the track data, which is then sent through the WebSocket connection to play the track in the voice channel. That is what we will be doing in this command. Lavalink can also play tracks directly from a media url, in which case the play command can look like this: ```csharp [Command] public async Task Play(CommandContext ctx, Uri url) { } ``` Like before, we will need to get our node and guild connection and have the appropriate checks. Since it wouldn't make sense to have the channel as a parameter, we will instead get it from the member's voice state: ```csharp //Important to check the voice state itself first, //as it may throw a NullReferenceException if they don't have a voice state. if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null) { await ctx.RespondAsync("You are not in a voice channel."); return; } var lava = ctx.Client.GetLavalink(); var node = lava.ConnectedNodes.Values.First(); var conn = node.GetGuildConnection(ctx.Member.VoiceState.Guild); if (conn == null) { await ctx.RespondAsync("Lavalink is not connected."); return; } ``` Next, we will get the track details by calling `node.Rest.GetTracksAsync()`. There are a variety of overloads for this: 1. `GetTracksAsync(LavalinkSearchType.Youtube, search)` will search YouTube for your search string. 2. `GetTracksAsync(LavalinkSearchType.SoundCloud, search)` will search SoundCloud for your search string. 3. `GetTracksAsync(Uri)` will use the direct url to obtain the track. This is mainly used for the other media sources. For this guide we will be searching YouTube. Let's pass in our search string and store the result in a variable: ```csharp //We don't need to specify the search type here //since it is YouTube by default. var loadResult = await node.Rest.GetTracksAsync(search); ``` The load result will contain an enum called `LoadResultType`, which will inform us if Lavalink was able to retrieve the track data. We can use this as a check: ```csharp //If something went wrong on Lavalink's end if (loadResult.LoadResultType == LavalinkLoadResultType.LoadFailed //or it just couldn't find anything. || loadResult.LoadResultType == LavalinkLoadResultType.NoMatches) { await ctx.RespondAsync($"Track search failed for {search}."); return; } ``` Lavalink will return the track data from your search in a collection called `loadResult.Tracks`, similar to using the search bar in YouTube or SoundCloud directly. The first track is typically the most accurate one, so that is what we will use: ```csharp var track = loadResult.Tracks.First(); ``` And finally, we can play the track: ```csharp await conn.PlayAsync(track); await ctx.RespondAsync($"Now playing {track.Title}!"); ``` Your play command should look like this: ```csharp [Command] public async Task Play(CommandContext ctx, [RemainingText] string search) { if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null) { await ctx.RespondAsync("You are not in a voice channel."); return; } var lava = ctx.Client.GetLavalink(); var node = lava.ConnectedNodes.Values.First(); var conn = node.GetGuildConnection(ctx.Member.VoiceState.Guild); if (conn == null) { await ctx.RespondAsync("Lavalink is not connected."); return; } var loadResult = await node.Rest.GetTracksAsync(search); if (loadResult.LoadResultType == LavalinkLoadResultType.LoadFailed || loadResult.LoadResultType == LavalinkLoadResultType.NoMatches) { await ctx.RespondAsync($"Track search failed for {search}."); return; } var track = loadResult.Tracks.First(); await conn.PlayAsync(track); await ctx.RespondAsync($"Now playing {track.Title}!"); } ``` Being able to pause the player is also useful. For this we can use most of the base from the play command: ```csharp [Command] public async Task Pause(CommandContext ctx) { if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null) { await ctx.RespondAsync("You are not in a voice channel."); return; } var lava = ctx.Client.GetLavalink(); var node = lava.ConnectedNodes.Values.First(); var conn = node.GetGuildConnection(ctx.Member.VoiceState.Guild); if (conn == null) { await ctx.RespondAsync("Lavalink is not connected."); return; } } ``` For this command we will also want to check the player state to determine if we should send a pause command. We can do so by checking `conn.CurrentState.CurrentTrack`: ```csharp if (conn.CurrentState.CurrentTrack == null) { await ctx.RespondAsync("There are no tracks loaded."); return; } ``` And finally, we can call pause: ```csharp await conn.PauseAsync(); ``` The finished command should look like so: ```csharp [Command] public async Task Pause(CommandContext ctx) { if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null) { await ctx.RespondAsync("You are not in a voice channel."); return; } var lava = ctx.Client.GetLavalink(); var node = lava.ConnectedNodes.Values.First(); var conn = node.GetGuildConnection(ctx.Member.VoiceState.Guild); if (conn == null) { await ctx.RespondAsync("Lavalink is not connected."); return; } if (conn.CurrentState.CurrentTrack == null) { await ctx.RespondAsync("There are no tracks loaded."); return; } await conn.PauseAsync(); } ``` -Of course, there are other commands Lavalink has to offer. Check out [the docs](https://dsp-nextgen.aitsys.dev/api/DSharpPlusNextGen.Lavalink.LavalinkGuildConnection.html#methods) to view the commands you can use while playing tracks. +Of course, there are other commands Lavalink has to offer. Check out [the docs](https://dsp-nextgen.aitsys.dev/api/DisCatSharp.Lavalink.LavalinkGuildConnection.html#methods) to view the commands you can use while playing tracks. diff --git a/docs/articles/audio/voicenext/transmit.md b/docs/articles/audio/voicenext/transmit.md index 69eeb8b52..7041ef69b 100644 --- a/docs/articles/audio/voicenext/transmit.md +++ b/docs/articles/audio/voicenext/transmit.md @@ -1,118 +1,118 @@ --- uid: voicenext_transmit title: Transmitting --- ## Transmitting with VoiceNext ### Enable VoiceNext -Install the `DSharpPlusNextGen.VoiceNext` package from NuGet. +Install the `DisCatSharp.VoiceNext` package from NuGet. ![NuGet Package Manager](/images/voicenext_transmit_01.png) Then use the `UseVoiceNext` extension method on your instance of `DiscordClient`. ```cs var discord = new DiscordClient(); discord.UseVoiceNext(); ``` ### Connect Joining a voice channel is *very* easy; simply use the `ConnectAsync` extension method on `DiscordChannel`. ```cs DiscordChannel channel; VoiceNextConnection connection = await channel.ConnectAsync(); ``` ### Transmit Discord requires that we send Opus encoded stereo PCM audio data at a sample rate of 48,000 Hz. You'll need to convert your audio source to PCM S16LE using your preferred program for media conversion, then read that data into a `Stream` object or an array of `byte` to be used with VoiceNext. Opus encoding of the PCM data will be done automatically by VoiceNext before sending it to Discord. This example will use [ffmpeg](https://ffmpeg.org/about.html) to convert an MP3 file to a PCM stream. ```cs var filePath = "funiculi_funicula.mp3"; var ffmpeg = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", Arguments = $@"-i ""{filePath}"" -ac 2 -f s16le -ar 48000 pipe:1", RedirectStandardOutput = true, UseShellExecute = false }); Stream pcm = ffmpeg.StandardOutput.BaseStream; ``` Now that our audio is the correct format, we'll need to get a *transmit sink* for the channel we're connected to. You can think of the transmit stream as our direct interface with a voice channel; any data written to one will be processed by VoiceNext, queued, and sent to Discord which will then be output to the connected voice channel. ```cs VoiceTransmitSink transmit = connection.GetTransmitSink(); ``` Once we have a transmit sink, we can 'play' our audio by copying our PCM data to the transmit sink buffer. ```cs await pcm.CopyToAsync(transmit); ``` `Stream#CopyToAsync()` will copy PCM data from the input stream to the output sink, up to the sink's configured capacity, at which point it will wait until it can copy more. This means that the call will hold the task's execution, until such time that the entire input stream has been consumed, and enqueued in the sink. This operation cannot be cancelled. If you'd like to have finer control of the playback, you should instead consider using `Stream#ReadAsync()` and `VoiceTransmitSink#WriteAsync()` to manually copy small portions of PCM data to the transmit sink. ### Disconnect Similar to joining, leaving a voice channel is rather straightforward. ```cs var vnext = discord.GetVoiceNext(); var connection = vnext.GetConnection(); connection.Disconnect(); ``` ## Example Commands ```cs [Command("join")] public async Task JoinCommand(CommandContext ctx, DiscordChannel channel = null) { channel ??= ctx.Member.VoiceState?.Channel; await channel.ConnectAsync(); } [Command("play")] public async Task PlayCommand(CommandContext ctx, string path) { var vnext = ctx.Client.GetVoiceNext(); var connection = vnext.GetConnection(ctx.Guild); var transmit = connection.GetTransmitSink(); var pcm = ConvertAudioToPcm(path); await pcm.CopyToAsync(transmit); await pcm.DisposeAsync(); } [Command("leave")] public async Task LeaveCommand(CommandContext ctx) { var vnext = ctx.Client.GetVoiceNext(); var connection = vnext.GetConnection(ctx.Guild); connection.Disconnect(); } private Stream ConvertAudioToPcm(string filePath) { var ffmpeg = Process.Start(new ProcessStartInfo { FileName = "ffmpeg", Arguments = $@"-i ""{filePath}"" -ac 2 -f s16le -ar 48000 pipe:1", RedirectStandardOutput = true, UseShellExecute = false }); return ffmpeg.StandardOutput.BaseStream; } ``` diff --git a/docs/articles/basics/first_bot.md b/docs/articles/basics/first_bot.md index 7b003ff58..46429c2cb 100644 --- a/docs/articles/basics/first_bot.md +++ b/docs/articles/basics/first_bot.md @@ -1,226 +1,226 @@ --- uid: basics_first_bot title: Your First Bot --- # Your First Bot >[!NOTE] > This article assumes the following: > * You have [created a bot account](xref:basics_bot_account "Creating a Bot Account") and have a bot token. > * You have [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) installed on your computer. ## Create a Project Open up Visual Studio and click on `Create a new project` towards the bottom right. ![Visual Studio Start Screen](/images/basics_first_bot_01.png)
Select `Console App (.NET Core)` then click on the `Next` button. ![New Project Screen](/images/basics_first_bot_02.png)
Next, you'll give your project a name. For this example, we'll name it `MyFirstBot`.
If you'd like, you can also change the directory that your project will be created in. Enter your desired project name, then click on the `Create` button. ![Name Project Screen](/images/basics_first_bot_03.png)
Voilà! Your project has been created! ![Visual Studio IDE](/images/basics_first_bot_04.png) ## Install Package -Now that you have a project created, you'll want to get DSharpPlusNextGen installed. +Now that you have a project created, you'll want to get DisCatSharp installed. Locate the *solution explorer* on the right side, then right click on `Dependencies` and select `Manage NuGet Packages` from the context menu. ![Dependencies Context Menu](/images/basics_first_bot_05.png)
You'll then be greeted by the NuGet package manager. -Select the `Browse` tab towards the top left, then type `DSharpPlusNextGen` into the search text box with the Pre-release checkbox checked **ON**. +Select the `Browse` tab towards the top left, then type `DisCatSharp` into the search text box with the Pre-release checkbox checked **ON**. ![NuGet Package Search](/images/basics_first_bot_06.png)
-The first results should be the six DSharpPlusNextGen packages. +The first results should be the six DisCatSharp packages. ![Search Results](/images/basics_first_bot_07.png) Package|Description :---: |:---: -`DSharpPlusNextGen`|Main package; Discord API client. -`DSharpPlusNextGen.CommandsNext`|Add-on which provides a command framework. -`DSharpPlusNextGen.Common`|Common tools & converters -`DSharpPlusNextGen.Interactivity`|Add-on which allows for interactive commands. -`DSharpPlusNextGen.Lavalink`|Client implementation for [Lavalink](xref:audio_lavalink_setup). Useful for music bots. -`DSharpPlusNextGen.SlashCommands`|Add-on which makes dealing with slash commands easyer. -`DSharpPlusNextGen.VoiceNext`|Add-on which enables connectivity to Discord voice channels. -`DSharpPlusNextGen.VoiceNext.Natives`|Voice next natives. +`DisCatSharp`|Main package; Discord API client. +`DisCatSharp.CommandsNext`|Add-on which provides a command framework. +`DisCatSharp.Common`|Common tools & converters +`DisCatSharp.Interactivity`|Add-on which allows for interactive commands. +`DisCatSharp.Lavalink`|Client implementation for [Lavalink](xref:audio_lavalink_setup). Useful for music bots. +`DisCatSharp.SlashCommands`|Add-on which makes dealing with slash commands easyer. +`DisCatSharp.VoiceNext`|Add-on which enables connectivity to Discord voice channels. +`DisCatSharp.VoiceNext.Natives`|Voice next natives.
-We'll only need the `DSharpPlusNextGen` package for the basic bot we'll be writing in this article.
+We'll only need the `DisCatSharp` package for the basic bot we'll be writing in this article.
Select it from the list then click the `Install` button to the right (after verifing that you will be installing the **latest 4.0 version**). ![Install DSharpPlus](/images/basics_first_bot_08.png) You're now ready to write some code! ## First Lines of Code -DSharpPlusNextGen implements [Task-based Asynchronous Pattern](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern). -Because of this, the majority of DSharpPlusNextGen methods must be executed in a method marked as `async` so they can be properly `await`ed. +DisCatSharp implements [Task-based Asynchronous Pattern](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern). +Because of this, the majority of DisCatSharp methods must be executed in a method marked as `async` so they can be properly `await`ed. Due to the way the compiler generates the underlying [IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) code, marking our `Main` method as `async` has the potential to cause problems. As a result, we must pass the program execution to an `async` method. Head back to your *Program.cs* tab and empty the `Main` method by deleting line 9. ![Code Editor](/images/basics_first_bot_09.png) Now, create a new `static` method named `MainAsync` beneath your `Main` method. Have it return type `Task` and mark it as `async`. After that, add `MainAsync().GetAwaiter().GetResult();` to your `Main` method. ```cs static void Main(string[] args) { MainAsync().GetAwaiter().GetResult(); } static async Task MainAsync() { } ``` If you typed this in by hand, Intellisense should have generated the required `using` directive for you.
However, if you copy-pasted the snippet above, VS will complain about being unable to find the `Task` type. Hover over `Task` with your mouse and click on `Show potential fixes` from the tooltip. ![Error Tooltip](/images/basics_first_bot_10.png) Then apply the recommended solution. ![Solution Menu](/images/basics_first_bot_11.png)
We'll now create a new `DiscordClient` instance in our brand new asynchronous method. Create a new variable in `MainAsync` and assign it a new `DiscordClient` instance, then pass an instance of `DiscordConfiguration` to its constructor. Create an object initializer for `DiscordConfiguration` and populate the `Token` property with your bot token then set the `TokenType` property to `TokenType.Bot`. -Next add the `Intents` Property and Populated it with the @DSharpPlusNextGen.DiscordIntents.AllUnprivileged value. These Intents +Next add the `Intents` Property and Populated it with the @DisCatSharp.DiscordIntents.AllUnprivileged value. These Intents are required for certain Events to be fired. Please visit this [article](xref:beyond_basics_intents) for more information. ```cs var discord = new DiscordClient(new DiscordConfiguration() { Token = "My First Token", TokenType = TokenType.Bot, Intents = DiscordIntents.AllUnprivileged }); ``` >[!WARNING] > We hard-code the token in the above snippet to keep things simple and easy to understand. > > Hard-coding your token is *not* a smart idea, especially if you plan on distributing your source code. - > Instead you should store your token in an external medium, such as a configuration file or environment variable, and read that into your program to be used with DSharpPlusNextGen. + > Instead you should store your token in an external medium, such as a configuration file or environment variable, and read that into your program to be used with DisCatSharp. Follow that up with `await discord.ConnectAsync();` to connect and login to Discord, and `await Task.Delay(-1);` at the end of the method to prevent the console window from closing prematurely. ```cs var discord = new DiscordClient(); await discord.ConnectAsync(); await Task.Delay(-1); ``` As before, Intellisense will have auto generated the needed `using` directive for you if you typed this in by hand.
If you've copied the snippet, be sure to apply the recommended suggestion to insert the required directive. -If you hit `F5` on your keyboard to compile and run your program, you'll be greeted by a happy little console with a single log message from DSharpPlusNextGen. Woo hoo! +If you hit `F5` on your keyboard to compile and run your program, you'll be greeted by a happy little console with a single log message from DisCatSharp. Woo hoo! ![Program Console](/images/basics_first_bot_12.png) ## Spicing Up Your Bot Right now our bot doesn't do a whole lot. Let's bring it to life by having it respond to a message! Hook the `MessageCreated` event fired by `DiscordClient` with a [lambda](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions).
Mark it as `async` and give it two parameters: `s` and `e`. ```cs discord.MessageCreated += async (s, e) => { }; ``` Then, add an `if` statement into the body of your event lambda that will check if `e.Message.Content` starts with your desired trigger word and respond with a message using `e.Message.RespondAsync` if it does. For this example, we'll have the bot to respond with *pong!* for each message that starts with *ping*. ```cs discord.MessageCreated += async (s, e) => { if (e.Message.Content.ToLower().StartsWith("ping")) await e.Message.RespondAsync("pong!"); }; ``` ## The Finished Product Your entire program should now look like this: ```cs using System; using System.Threading.Tasks; -using DSharpPlusNextGen; +using DisCatSharp; namespace MyFirstBot { class Program { static void Main(string[] args) { MainAsync().GetAwaiter().GetResult(); } static async Task MainAsync() { var discord = new DiscordClient(new DiscordConfiguration() { Token = "My First Token", TokenType = TokenType.Bot }); discord.MessageCreated += async (s, e) => { if (e.Message.Content.ToLower().StartsWith("ping")) await e.Message.RespondAsync("pong!"); }; await discord.ConnectAsync(); await Task.Delay(-1); } } } ``` Hit `F5` to run your bot, then send *ping* in any channel your bot account has access to.
Your bot should respond with *pong!* for each *ping* you send. Congrats, your bot now does something! ![Bot Response](/images/basics_first_bot_13.png) ## Further Reading Now that you have a basic bot up and running, you should take a look at the following: * [Events](xref:beyond_basics_events) * [CommandsNext](xref:commands_intro) diff --git a/docs/articles/beyond_basics/intents.md b/docs/articles/beyond_basics/intents.md index bbf97aeba..716b832cf 100644 --- a/docs/articles/beyond_basics/intents.md +++ b/docs/articles/beyond_basics/intents.md @@ -1,62 +1,62 @@ --- uid: beyond_basics_intents title: Intents --- ## Intents Intents were added to Discord to help the service not have to push so many events to the bots that were not using them. If you are going to be needing to subscribe to any type of event, they are going to have to be defined **BOTH** within the -[Discord Application under the Bot Page](https://discord.com/developers/applications) on Discords Site and also within the @DSharpPlusNextGen.DiscordConfiguration. +[Discord Application under the Bot Page](https://discord.com/developers/applications) on Discords Site and also within the @DisCatSharp.DiscordConfiguration. ### Discord Application On the [Discord Application under the Bot Page](https://discord.com/developers/applications) you will have to specify if your bot requires Privileged Intents. We recommend having these all enabled at first to ensure the most stability when building your first bot, otherwise you may run into issues when retrieving entities from the library's cache. ![Bot Page](/images/Intents.png) >[!WARNING] > These privileged intents may not be available for you to toggle on immediately. > > Due to their nature of sensitive data, Discord requires you to go through a verification process once your bot is in a certain amount of servers. > Please read this [blog post](https://support.discord.com/hc/en-us/articles/360040720412-Bot-Verification-and-Data-Whitelisting) for more information and how to apply. ### Discord Configuration Within your `DiscordConfiguration` you will have to specify all the intents you will need. Here is a list of all the -[Intents](xref:DSharpPlusNextGen.DiscordIntents) DSharpPlus Supports. By default, the configuration will use `DiscordIntents.AllUnprivileged` as the default value. Like above however, we recommend having all intents enabled at first, so you should specify `DiscordIntents.All` in your configuration which will include the privleged intents you enabled in your application: +[Intents](xref:DisCatSharp.DiscordIntents) DSharpPlus Supports. By default, the configuration will use `DiscordIntents.AllUnprivileged` as the default value. Like above however, we recommend having all intents enabled at first, so you should specify `DiscordIntents.All` in your configuration which will include the privleged intents you enabled in your application: ```csharp var config = new DiscordConfiguration() { Intents = DiscordIntents.All }; ``` When you become more advanced, you can try experimenting with turning off intents you do not need in order to save resources. In your `DiscordConfiguration` you can specify one or many. Here is an example of just specifying one: ```csharp var config = new DiscordConfiguration() { Intents = DiscordIntents.GuildMessages }; ``` Here is an example of specifying many: ```csharp var config = new DiscordConfiguration() { Intents = DiscordIntents.DirectMessageReactions | DiscordIntents.DirectMessages | DiscordIntents.GuildBans | DiscordIntents.GuildEmojis | DiscordIntents.GuildInvites | DiscordIntents.GuildMembers | DiscordIntents.GuildMessages | DiscordIntents.Guilds | DiscordIntents.GuildVoiceStates | DiscordIntents.GuildWebhooks, }; ``` Please Note, if you specify a privileged intent within your `DiscordConfiguration` that you have not signed up for on the Discord Application page, an error will be thrown on the connection. diff --git a/docs/articles/commands/command_attributes.md b/docs/articles/commands/command_attributes.md index 46dca6997..9600cdcff 100644 --- a/docs/articles/commands/command_attributes.md +++ b/docs/articles/commands/command_attributes.md @@ -1,103 +1,103 @@ --- uid: commands_command_attributes title: Command Attributes --- ## Built-In Attributes CommandsNext has a variety of built-in attributes to enhance your commands and provide some access control. The majority of these attributes can be applied to your command methods and command groups. -- @DSharpPlusNextGen.CommandsNext.Attributes.AliasesAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.CooldownAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.DescriptionAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.DontInjectAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.HiddenAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.ModuleLifespanAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.PriorityAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RemainingTextAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireBotPermissionsAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireCommunityAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireDirectMessageAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireDiscordCertifiedModeratorAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireDiscordEmployeeAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireGuildAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireGuildOwnerAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireMemberVerificationGateAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireNsfwAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireOwnerAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireOwnerOrIdAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequirePermissionsAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequirePrefixesAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireRolesAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireUserPermissionsAttribute -- @DSharpPlusNextGen.CommandsNext.Attributes.RequireWelcomeScreenAttribute +- @DisCatSharp.CommandsNext.Attributes.AliasesAttribute +- @DisCatSharp.CommandsNext.Attributes.CooldownAttribute +- @DisCatSharp.CommandsNext.Attributes.DescriptionAttribute +- @DisCatSharp.CommandsNext.Attributes.DontInjectAttribute +- @DisCatSharp.CommandsNext.Attributes.HiddenAttribute +- @DisCatSharp.CommandsNext.Attributes.ModuleLifespanAttribute +- @DisCatSharp.CommandsNext.Attributes.PriorityAttribute +- @DisCatSharp.CommandsNext.Attributes.RemainingTextAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireBotPermissionsAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireCommunityAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireDirectMessageAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireDiscordCertifiedModeratorAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireDiscordEmployeeAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireGuildAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireGuildOwnerAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireMemberVerificationGateAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireNsfwAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireOwnerAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireOwnerOrIdAttribute +- @DisCatSharp.CommandsNext.Attributes.RequirePermissionsAttribute +- @DisCatSharp.CommandsNext.Attributes.RequirePrefixesAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireRolesAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireUserPermissionsAttribute +- @DisCatSharp.CommandsNext.Attributes.RequireWelcomeScreenAttribute ## Custom Attributes If the above attributes don't meet your needs, CommandsNext also gives you the option of writing your own! Simply create a new class which inherits from `CheckBaseAttribute` and implement the required method. Our example below will only allow a command to be ran during a specified year. ```cs public class RequireYearAttribute : CheckBaseAttribute { public int AllowedYear { get; private set; } public RequireYearAttribute(int year) { AllowedYear = year; } public override Task ExecuteCheckAsync(CommandContext ctx, bool help) { return Task.FromResult(AllowedYear == DateTime.Now.Year); } } ```
You'll also need to apply the `AttributeUsage` attribute to your attribute.
For our example attribute, we'll set it to only be usable once on methods. ```cs [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class RequireYearAttribute : CheckBaseAttribute { // ... } ``` You can provide feedback to the user using the `CommandsNextExtension#CommandErrored` event. ```cs private async Task MainAsync() { var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.CommandErrored += CmdErroredHandler; } private async Task CmdErroredHandler(CommandsNextExtension _, CommandErrorEventArgs e) { var failedChecks = ((ChecksFailedException)e.Exception).FailedChecks; foreach (var failedCheck in failedChecks) { if (failedCheck is RequireYearAttribute) { var yearAttribute = (RequireYearAttribute)failedCheck; await e.Context.RespondAsync($"Only usable during year {yearAttribute.AllowedYear}."); } } } ```
Once you've got all of that completed, you'll be able to use it on a command! ```cs [Command("generic"), RequireYear(2030)] public async Task GenericCommand(CommandContext ctx, string generic) { await ctx.RespondAsync("Generic response."); } ``` ![Generic Image](/images/commands_command_attributes_01.png) diff --git a/docs/articles/commands/intro.md b/docs/articles/commands/intro.md index 5f5c80049..dafb6fa57 100644 --- a/docs/articles/commands/intro.md +++ b/docs/articles/commands/intro.md @@ -1,330 +1,330 @@ --- uid: commands_intro title: CommandsNext Introduction --- >[!NOTE] > This article assumes you've recently read the article on *[writing your first bot](xref:basics_first_bot)*. # Introduction to CommandsNext This article will introduce you to some basic concepts of our native command framework: *CommandsNext*.
-Be sure to install the `DSharpPlusNextGen.CommandsNext` package from NuGet before continuing. +Be sure to install the `DisCatSharp.CommandsNext` package from NuGet before continuing. ![CommandsNext NuGet Package](/images/commands_intro_01.png)
## Writing a Basic Command ### Create a Command Module A command module is simply a class which acts as a container for your command methods. Instead of registering individual commands, you'd register a single command module which contains multiple commands. There's no limit to the amount of modules you can have, and no limit to the amount of commands each module can contain. For example: you could have a module for moderation commands and a separate module for image commands. This will help you keep your commands organized and reduce the clutter in your project. Our first demonstration will be simple, consisting of one command module with a simple command.
We'll start by creating a new folder named `Commands` which contains a new class named `MyFirstModule`. ![Solution Explorer](/images/commands_intro_02.png) Give this new class `public` access and have it inherit from `BaseCommandModule`. ```cs public class MyFirstModule : BaseCommandModule { } ``` ### Create a Command Method Within our new module, create a method named `GreetCommand` marked as `async` with a `Task` return type. The first parameter of your method *must* be of type `CommandContext`, as required by CommandsNext. ```cs public async Task GreetCommand(CommandContext ctx) { } ``` In the body of our new method, we'll use `CommandContext#RespondAsync` to send a simple message. ```cs await ctx.RespondAsync("Greetings! Thank you for executing me!"); ``` Finally, mark your command method with the `Command` attribute so CommandsNext will know to treat our method as a command method. This attribute takes a single parameter: the name of the command. We'll name our command *greet* to match the name of the method. ```cs [Command("greet")] public async Task GreetCommand(CommandContext ctx) { await ctx.RespondAsync("Greetings! Thank you for executing me!"); } ```
Your command module should now resemble this: ```cs using System.Threading.Tasks; -using DSharpPlusNextGen.CommandsNext; -using DSharpPlusNextGen.CommandsNext.Attributes; +using DisCatSharp.CommandsNext; +using DisCatSharp.CommandsNext.Attributes; public class MyFirstModule : BaseCommandModule { [Command("greet")] public async Task GreetCommand(CommandContext ctx) { await ctx.RespondAsync("Greetings! Thank you for executing me!"); } } ``` ### Cleanup and Configuration Before we can run our new command, we'll need modify our main method.
Start by removing the event handler we created [previously](xref:basics_first_bot#spicing-up-your-bot). ```cs var discord = new DiscordClient(); discord.MessageCreated += async (s, e) => // REMOVE { // ALL if (e.Message.Content.ToLower().StartsWith("ping")) // OF await e.Message.RespondAsync("pong!"); // THESE }; // LINES await discord.ConnectAsync(); ```
Next, call the `UseCommandsNext` extension method on your `DiscordClient` instance and pass it a new `CommandsNextConfiguration` instance. Assign the resulting `CommandsNextExtension` instance to a new variable named *commands*. This important step will enable CommandsNext for your Discord client. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(new CommandsNextConfiguration()); ``` Create an object initializer for `CommandsNextConfiguration` and assign the `StringPrefixes` property a new `string` array containing your desired prefixes. Our example below will only define a single prefix: `!`. ```cs new CommandsNextConfiguration() { StringPrefixes = new[] { "!" } } ```
Now we'll register our command module. Call the `RegisterCommands` method on our `CommandsNextExtension` instance and provide it with your command module. ```cs var discord = new DiscordClient(); var commands = discord.UseCommandsNext(); commands.RegisterCommands(); await discord.ConnectAsync(); ``` Alternatively, you can pass in your assembly to register commands from all modules in your program. ```cs commands.RegisterCommands(Assembly.GetExecutingAssembly()); ```
Your main method should look similar to the following: ```cs internal static async Task MainAsync() { var discord = new DiscordClient(new DiscordConfiguration()); var commands = discord.UseCommandsNext(new CommandsNextConfiguration() { StringPrefixes = new[] { "!" } }); commands.RegisterCommands(); await discord.ConnectAsync(); await Task.Delay(-1); } ``` ### Running Your Command It's now the moment of truth; all your blood, sweat, and tears have lead to this moment. Hit `F5` on your keyboard to compile and run your bot, then execute your command in any channel that your bot account has access to. ![Congratulations, You've Won!](/images/commands_intro_03.png) [That was easy](https://www.youtube.com/watch?v=GsQXadrmhws).
## Taking User Input ### Command Arguments Now that we have a basic command down, let's spice it up a bit by defining *arguments* to accept user input. Defining an argument is simple; just add additional parameters to your signature of your command method. CommandsNext will automatically parse user input and populate the parameters of your command method with those arguments. To demonstrate, we'll modify our *greet* command to greet a user with a given name. Head back to `MyFirstModule` and add a parameter of type `string` to the `GreetCommand` method. ```cs [Command("greet")] public async Task GreetCommand(CommandContext ctx, string name) ``` CommandsNext will now interpret this as a command named *greet* that takes one argument. Next, replace our original response message with an [interpolated string](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) which uses our new parameter. ```cs public async Task GreetCommand(CommandContext ctx, string name) { await ctx.RespondAsync($"Greetings, {name}! You're pretty neat!"); } ``` That's all there is to it. Smack `F5` and test it out in a channel your bot account has access to. ![Greet Part 2: Electric Boogaloo](/images/commands_intro_04.png)
Now, you may have noticed that providing more than one word simply does not work.
For example, `!greet Luke Smith` will result in no response from your bot. This fails because a valid [overload](#command-overloads) could not be found for your command. CommandsNext will split arguments by whitespace. This means `Luke Smith` is counted as two separate arguments; `Luke` and `Smith`. In addition to this, CommandsNext will attempt to find and execute an overload of your command that has the *same number* of provided arguments. Together, this means that any additional arguments will prevent CommandsNext from finding a valid overload to execute. The simplest way to get around this would be to wrap your input with double quotes.
CommandsNext will parse this as one argument, allowing your command to be executed. ``` !greet "Luke Smith" ``` If you would prefer not to use quotes, you can use the `RemainingText` attribute on your parameter.
This attribute will instruct CommandsNext to parse all remaining arguments into that parameter. ```cs public async Task GreetCommand(CommandContext ctx, [RemainingText] string name) ``` Alternatively, you can use the `params` keyword to have all remaining arguments parsed into an array. ```cs public async Task GreetCommand(CommandContext ctx, params string[] names) ``` A more obvious solution is to add additional parameters to the method signature of your command method.
```cs public async Task GreetCommand(CommandContext ctx, string firstName, string lastName) ```
Each of these has their own caveats; it'll be up to you to choose the best solution for your commands. ### Argument Converters CommandsNext can convert arguments, which are natively `string`, to the type specified by a command method parameter. This functionality is powered by *argument converters*, and it'll help to eliminate the boilerplate code needed to parse and convert `string` arguments. CommandsNext has built-in argument converters for the following types: Category|Types :---:|:--- Discord|`DiscordGuild`, `DiscordChannel`, `DiscordMember`, `DiscordUser`,
`DiscordRole`, `DiscordMessage`, `DiscordEmoji`, `DiscordColor` Integral|`byte`, `short`, `int`, `long`, `sbyte`, `ushort`, `uint`, `ulong` Floating-Point|`float`, `double`, `decimal` Date|`DateTime`, `DateTimeOffset`, `TimeSpan` Character|`string`, `char` Boolean|`bool` You're also able to create and provide your own [custom argument converters](xref:commands_argument_converters), if desired.
Let's do a quick demonstration of the built-in converters. Create a new command method above our `GreetCommand` method named `RandomCommand` and have it take two integer arguments. As the method name suggests, this command will be named *random*. ```cs [Command("random")] public async Task RandomCommand(CommandContext ctx, int min, int max) { } ``` Make a variable with a new instance of `Random`. ```cs var random = new Random(); ``` Finally, we'll respond with a random number within the range provided by the user. ```cs await ctx.RespondAsync($"Your number is: {random.Next(min, max)}"); ``` Run your bot once more with `F5` and give this a try in a text channel. ![Discord Channel](/images/commands_intro_05.png) CommandsNext converted the two arguments from `string` into `int` and passed them to the parameters of our command, removing the need to manually parse and convert the arguments yourself.
We'll do one more to drive the point home. Head back to our old `GreetCommand` method, remove our `name` parameter, and replace it with a new parameter of type `DiscordMember` named `member`. ```cs public async Task GreetCommand(CommandContext ctx, DiscordMember member) ``` Then modify the response to mention the provided member with the `Mention` property on `DiscordMember`. ```cs public async Task GreetCommand(CommandContext ctx, DiscordMember member) { await ctx.RespondAsync($"Greetings, {member.Mention}! Enjoy the mention!"); } ``` Go ahead and give that a test run. ![According to all known laws of aviation,](/images/commands_intro_06.png) ![there is no way a bee should be able to fly.](/images/commands_intro_07.png) ![Its wings are too small to get its fat little body off the ground.](/images/commands_intro_08.png) The argument converter for `DiscordMember` is able to parse mentions, usernames, nicknames, and user IDs then look for a matching member within the guild the command was executed from. Ain't that neat? ## Command Overloads Command method overloading allows you to create multiple argument configurations for a single command. ```cs [Command("foo")] public Task FooCommand(CommandContext ctx, string bar, int baz) { } [Command("foo")] public Task FooCommand(CommandContext ctx, DiscordUser bar) { } ``` Executing `!foo green 5` will run the first method, and `!foo @Emzi0767` will run the second method.
Additionally, all check attributes are shared between overloads.
```cs [Command("foo"), Aliases("bar", "baz")] [RequireGuild, RequireBotPermissions(Permissions.AttachFiles)] public Task FooCommand(CommandContext ctx, int bar, int baz, string qux = "agony") { } [Command("foo")] public Task FooCommand(CommandContext ctx, DiscordChannel bar, TimeSpan baz) { } ``` The additional attributes and checks applied to the first method will also be applied to the second method. ## Further Reading Now that you've gotten an understanding of CommandsNext, it'd be a good idea check out the following: * [Command Attributes](xref:commands_command_attributes) * [Help Formatter](xref:commands_help_formatter) * [Dependency Injection](xref:commands_dependency_injection) diff --git a/docs/articles/interactivity.md b/docs/articles/interactivity.md index d25cac734..85b8e528f 100644 --- a/docs/articles/interactivity.md +++ b/docs/articles/interactivity.md @@ -1,120 +1,120 @@ --- uid: interactivity title: Interactivity Introduction --- # Introduction to Interactivity Interactivity will enable you to write commands which the user can interact with through reactions and messages. The goal of this article is to introduce you to the general flow of this extension. -Make sure to install the `DSharpPlusNextGen.Interactivity` package from NuGet before continuing. +Make sure to install the `DisCatSharp.Interactivity` package from NuGet before continuing. ![Interactivity NuGet](/images/interactivity_01.png) ## Enabling Interactivity Interactivity can be registered using the `DiscordClient#UseInteractivity()` extension method.
Optionally, you can also provide an instance of `InteractivityConfiguration` to modify default behaviors. ```cs var discord = new DiscordClient(); discord.UseInteractivity(new InteractivityConfiguration() { PollBehaviour = PollBehaviour.KeepEmojis, Timeout = TimeSpan.FromSeconds(30) }); ``` ## Using Interactivity There are two ways available to use interactivity: * Extension methods available for `DiscordChannel` and `DiscordMessage`. -* [Instance methods](xref:DSharpPlusNextGen.Interactivity.InteractivityExtension#methods) available from `InteractivityExtension`. +* [Instance methods](xref:DisCatSharp.Interactivity.InteractivityExtension#methods) available from `InteractivityExtension`. We'll have a quick look at a few common interactivity methods along with an example of use for each.
The first (and arguably most useful) extension method is `SendPaginatedMessageAsync` for `DiscordChannel`. This method displays a collection of *'pages'* which are selected one-at-a-time by the user through reaction buttons. Each button click will move the page view in one direction or the other until the timeout is reached. You'll need to create a collection of pages before you can invoke this method. This can be done easily using the `GeneratePagesInEmbed` and `GeneratePagesInContent` instance methods from `InteractivityExtension`.
Alternatively, for pre-generated content, you can create and add individual instances of `Page` to a collection. This example will use the `GeneratePagesInEmbed` method to generate the pages. ```cs public async Task PaginationCommand(CommandContext ctx) { var reallyLongString = "Lorem ipsum dolor sit amet, consectetur adipiscing ..." var interactivity = ctx.Client.GetInteractivity(); var pages = interactivity.GeneratePagesInEmbed(reallyLongString); await ctx.Channel.SendPaginatedMessageAsync(ctx.Member, pages); } ``` ![Pagination Pages](/images/interactivity_02.png)
Next we'll look at the `WaitForReactionAsync` extension method for `DiscordMessage`.
This method waits for a reaction from a specific user and returns the emoji that was used. An overload of this method also enables you to wait for a *specific* reaction, as shown in the example below. ```cs public async Task ReactionCommand(CommandContext ctx, DiscordMember member) { var emoji = DiscordEmoji.FromName(ctx.Client, ":ok_hand:"); var message = await ctx.RespondAsync($"{member.Mention}, react with {emoji}."); var result = await message.WaitForReactionAsync(member, emoji); if (!result.TimedOut) await ctx.RespondAsync("Thank you!"); } ``` ![Thank You!](/images/interactivity_03.png)
Another reaction extension method for `DiscordMessage` is `CollectReactionsAsync`.
As the name implies, this method collects all reactions on a message until the timeout is reached. ```cs public async Task CollectionCommand(CommandContext ctx) { var message = await ctx.RespondAsync("React here!"); var reactions = await message.CollectReactionsAsync(); var strBuilder = new StringBuilder(); foreach (var reaction in reactions) { strBuilder.AppendLine($"{reaction.Emoji}: {reaction.Total}"); } await ctx.RespondAsync(strBuilder.ToString()); } ``` ![Reaction Count](/images/interactivity_04.png)
The final one we'll take a look at is the `GetNextMessageAsync` extension method for `DiscordMessage`.
This method will return the next message sent from the author of the original message.
Our example here will use its alternate overload which accepts an additional predicate. ```cs public async Task ActionCommand(CommandContext ctx) { await ctx.RespondAsync("Respond with *confirm* to continue."); var result = await ctx.Message.GetNextMessageAsync(m => { return m.Content.ToLower() == "confirm"; }); if (!result.TimedOut) await ctx.RespondAsync("Action confirmed."); } ``` ![Confirmed](/images/interactivity_05.png) diff --git a/docs/articles/misc/nightly_builds.md b/docs/articles/misc/nightly_builds.md index d66d7c3a1..2aea443b2 100644 --- a/docs/articles/misc/nightly_builds.md +++ b/docs/articles/misc/nightly_builds.md @@ -1,23 +1,23 @@ --- uid: misc_nightly_builds title: Nightly Builds --- # I like living on the edge - give me the freshest builds -We offer nightly builds for DSharpPlusNextGen. They contain bugfixes and new features before the NuGet releases, however they are +We offer nightly builds for DisCatSharp. They contain bugfixes and new features before the NuGet releases, however they are not guaranteed to be stable, or work at all. Open the NuGet interface for your project, check **Prerelease**. -Then just select **Latest prerelease** version of DSharpPlusNextGen packages, and install them. +Then just select **Latest prerelease** version of DisCatSharp packages, and install them. You might need to restart Visual Studio for changes to take effect. If you find any problems in the SlimGet versions of the packages, please follow the instructions in [Reporting issues](xref:misc_reporting_issues) article. ## But I'm running GNU/Linux, Mac OS X, or BSD! If you're running on a non-Windows OS, you'll have to get your hands dirty. Prepare your text editor and file browser. Run `dotnet restore`, it should be able to restore the packages without problems. diff --git a/docs/articles/preamble.md b/docs/articles/preamble.md index 577211ca7..d16d57637 100644 --- a/docs/articles/preamble.md +++ b/docs/articles/preamble.md @@ -1,39 +1,39 @@ --- uid: preamble title: Article Preamble --- ## Knowledge Prerequisites Before attempting to write a Discord bot, you should be familiar with the concepts of [Object Oriented Programing](https://en.wikipedia.org/wiki/Object-oriented_programming), [the C# programming language](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/), and [Task-based Asynchronous Pattern](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap). If you're brand new to C#, or programming in general, this library may prove difficult for you to use.
Fortunately, there are resources that can help you get started with the language! An excellent tutorial series to go through would be [C# Fundamentals for Absolute Beginners](https://channel9.msdn.com/Series/CSharp-Fundamentals-for-Absolute-Beginners) by Bob Tabor. His videos go through all the basics, from setting up your development environment up to some of the more advanced concepts. If you're not sure what to do first, Bob's tutorial series should be your starting point! ## Supported .NET Implementations -Because DSharpPlusNextGen targets .NET Standard 2.0, there are many implementations that may function with DSharpPlusNextGen. +Because DisCatSharp targets .NET Standard 2.0, there are many implementations that may function with DisCatSharp. However, there are only a few versions we will *explicitly* provide support for. Implementation|Support|Notes :---: |:---:|:--- [.NET Core](https://en.wikipedia.org/wiki/.NET_Core)|✔️|LTS versions 2.1 and 3.1 are supported. [.NET Framework](https://en.wikipedia.org/wiki/.NET_Framework)|⚠️|Versions 4.6.1 through 4.8 *should* work fine.
However, we do not directly support .NET Framework.
We recommend that you use the latest LTS version of .NET Core. [Mono](https://en.wikipedia.org/wiki/Mono_(software))|❌️|Has numerous flaws which can break things without warning.
If you need a cross platform runtime, use .NET Core. -[Unity](https://en.wikipedia.org/wiki/Unity_(game_engine))|❌️|Game engines with C# support will never be supported by DSharpPlusNextGen. You should consider using the official [Discord GameSDK](https://discord.com/developers/docs/game-sdk/sdk-starter-guide) instead. +[Unity](https://en.wikipedia.org/wiki/Unity_(game_engine))|❌️|Game engines with C# support will never be supported by DisCatSharp. You should consider using the official [Discord GameSDK](https://discord.com/developers/docs/game-sdk/sdk-starter-guide) instead. If you use an unsupported implementation and encounter issues, you'll be on your own. ## Getting Started If you're writing a Discord bot for the first time, you'll want to start with *[creating a bot account](xref:basics_bot_account)*.
Otherwise, if you have a bot account already, start off with the *[writing your first bot](xref:basics_first_bot)* article.
Once you're up and running, feel free to browse through the [API Documentation](/api/index.html)! ## Support and Questions You can get in contact with us on Discord through one of the following guilds: -**DSharpPlusNextGen Guild**:
+**DisCatSharp Guild**:
[![Project Nyaw~](https://discordapp.com/api/guilds/858089281214087179/embed.png?style=banner2)](https://discord.gg/CPhrqxu2SF) diff --git a/docs/faq.md b/docs/faq.md index bbd166e57..52d4124b0 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,84 +1,84 @@ --- uid: faq title: Frequently Asked Questions --- # Frequently Asked Questions ### Code I copied from an article isn't compiling or working as expected. Why? *Please use the code snippets as a reference; don't blindly copy-paste code!* The snippets of code in the articles are meant to serve as examples to help you understand how to use a part of the library.
Although most will compile and work at the time of writing, changes to the library over time can make some snippets obsolete.
Many issues can be resolved with Intellisense by searching for similarly named methods and verifying method parameters. ### I'm targeting Mono and have exceptions, crashes, or other problems. As mentioned in the [preamble](xref:preamble), the Mono runtime is inherently unstable and has numerous flaws.
Because of this we do not support Mono in any way, nor will we support any other projects which use it. Instead, we recommend using either the latest LTS release or most recent stable version of [.NET Core](https://dotnet.microsoft.com/download). ### Connecting to a voice channel with VoiceNext will either hang or throw an exception. To troubleshoot, please ensure that: -* You are using the latest version of DSharpPlusNextGen. -* You have properly enabled VoiceNext with your instance of @DSharpPlusNextGen.DiscordClient. +* You are using the latest version of DisCatSharp. +* You have properly enabled VoiceNext with your instance of @DisCatSharp.DiscordClient. * You are *not* using VoiceNext in an event handler. * You have [opus and libsodium](xref:voicenext_prerequisites) available in your target environment. ### Why am I getting *heartbeat skipped* message in my console? There are two possible reasons: #### Connection issue between your bot application and Discord. Check your internet connection and ensure that the machine your bot is hosted on has a stable internet connection.
If your local network has no issues, the problem could be with either Discord or Cloudfare. In which case, it's out of your control. #### Complex, long-running code in an event handler. Any event handlers that have the potential to run for more than a few seconds could cause a deadlock, and cause several heartbeats to be skipped. Please take a look at our short article on [handling DSharpPlus exceptions](xref:beyond_basics_events) to learn how to avoid this. ### Why am I getting a 4XX error and how can I fix it? HTTP Error Code|Cause|Resolution :---:|:---|:--- `401`|Invalid token.|Verify your token and make sure no errors were made.
The client secret found on the 'general information' tab of your application page *is not* your token. `403`|Not enough permissions.|Verify permissions and ensure your bot account has a role higher than the target user.
Administrator permissions *do not* bypass the role hierarchy. `404`|Requested object not found.|This usually means the entity does not exist. You should reattempt then inform your user. ### I cannot modify a specific user or role. Why is this? In order to modify a user, the highest role of your bot account must be higher than the target user.
Changing the properties of a role requires that your bot account have a role higher than that role. ### Does CommandsNext support dependency injection? It does! Please take a look at our [article](xref:commands_dependency_injection) on the subject. ### Can I use a user token? -Automating a user account is against Discord's [Terms of Service](https://dis.gd/terms) and is not supported by DSharpPlusNextGen. +Automating a user account is against Discord's [Terms of Service](https://dis.gd/terms) and is not supported by DisCatSharp. ### How can I set a custom status? If you mean a *true* custom status like this: ![help](/images/faq_01.png) No, you cannot. Discord does not allow bots to use custom statuses.
However, if you meant an activity like this: ![Bot Presence](/images/faq_02.png) You can use either of the following -* The overload for @DSharpPlusNextGen.DiscordClient.ConnectAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) which accepts a @DSharpPlusNextGen.Entities.DiscordActivity. -* @DSharpPlusNextGen.DiscordClient.UpdateStatusAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) OR @DSharpPlusNextGen.DiscordShardedClient.UpdateStatusAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) (for the sharded client) at any point after `Ready` has been fired. +* The overload for @DisCatSharp.DiscordClient.ConnectAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) which accepts a @DisCatSharp.Entities.DiscordActivity. +* @DisCatSharp.DiscordClient.UpdateStatusAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) OR @DisCatSharp.DiscordShardedClient.UpdateStatusAsync(DiscordActivity,System.Nullable{UserStatus},System.Nullable{DateTimeOffset}) (for the sharded client) at any point after `Ready` has been fired. -### Am I able to retrieve a @DSharpPlusNextGen.Entities.DiscordRole by name? -Yes. Use LINQ on the `Roles` property of your instance of @DSharpPlusNextGen.Entities.DiscordGuild and compare against the `Name` of each @DSharpPlusNextGen.Entities.DiscordRole. +### Am I able to retrieve a @DisCatSharp.Entities.DiscordRole by name? +Yes. Use LINQ on the `Roles` property of your instance of @DisCatSharp.Entities.DiscordGuild and compare against the `Name` of each @DisCatSharp.Entities.DiscordRole. ### Why are you using Newtonsoft.Json when System.Text.Json is available Yes `System.Text.Json` is available to use but it still doesnt stand up to what we currently need which is why we still use Newtonsoft.Json. Maybe in time we can switch to your favorite Json Deserializer but for right now we will be using Newtonsoft.Json for the forseeable future. ### Why the hell are my events not firing? -This is because in the Discord V8 API, they require @DSharpPlusNextGen.DiscordIntents to be enabled on @DSharpPlusNextGen.DiscordConfiguration and the +This is because in the Discord V8 API, they require @DisCatSharp.DiscordIntents to be enabled on @DisCatSharp.DiscordConfiguration and the Discord Application Portal. We have an [article](xref:beyond_basics_intents) that covers all that has to be done to set this up.