Viewing entries tagged
discord

C# Discord Bot: Command Handling

C# Discord Bot: Command Handling

This post will go over command handling with Discord.Net. Whether you are following along from the Raspberry Pi series, or are just curious about how to do command handling with Discord.Net, welcome!

Prerequisites

  • .NET Core 2.x

  • …and we will be building upon the framework laid out here:

If you want to follow along with the finished code, go here: https://github.com/gngrninja/csharpi/tree/02-command-basics.

If you would like the starter code, and want to try building off of it yourself, go here: https://github.com/gngrninja/csharpi/tree/intro.

Adding the Command Handler

The first thing we’ll want to do is add the command handling service.

This service will be responsible for:

  • Hooking into the MessageReceivedAsync and CommandExecutedAsync events to process messages as they come in (to see if they are a valid command), and handle command execution (success/failure/not found actions)

  • Utilizing Dependency Injection in .NET Core to setup and pass through services/configurations

  • Loading all the command modules that inherit from ModuleBase

Command Handler Service Creation

The first thing we want to do here is create the command handling service.

  1. Create a folder named Services, and under that folder a file named CommandHandler.cs.

services_folder.png
CommandHandler.png

2. Here is the code for the command handling service, with comments to help understand what is happening (don’t worry too much about not understanding what is happening, yet!):

You can always view the most updated code, here: https://github.com/gngrninja/csharpi/blob/02-command-basics/Services/CommandHandler.cs.

using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.Configuration;

namespace csharpi.Services
{
    public class CommandHandler
    {
        // setup fields to be set later in the constructor
        private readonly IConfiguration _config;
        private readonly CommandService _commands;
        private readonly DiscordSocketClient _client;
        private readonly IServiceProvider _services;

        public CommandHandler(IServiceProvider services)
        {
            // juice up the fields with these services
            // since we passed the services in, we can use GetRequiredService to pass them into the fields set earlier
            _config = services.GetRequiredService<IConfiguration>();
            _commands = services.GetRequiredService<CommandService>();
            _client = services.GetRequiredService<DiscordSocketClient>();
            _services = services;
            
            // take action when we execute a command
            _commands.CommandExecuted += CommandExecutedAsync;

            // take action when we receive a message (so we can process it, and see if it is a valid command)
            _client.MessageReceived += MessageReceivedAsync;
        }

        public async Task InitializeAsync()
        {
            // register modules that are public and inherit ModuleBase<T>.
            await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
        }

        // this class is where the magic starts, and takes actions upon receiving messages
        public async Task MessageReceivedAsync(SocketMessage rawMessage)
        {
            // ensures we don't process system/other bot messages
            if (!(rawMessage is SocketUserMessage message)) 
            {
                return;
            }
            
            if (message.Source != MessageSource.User) 
            {
                return;
            }

            // sets the argument position away from the prefix we set
            var argPos = 0;

            // get prefix from the configuration file
            char prefix = Char.Parse(_config["Prefix"]);

            // determine if the message has a valid prefix, and adjust argPos based on prefix
            if (!(message.HasMentionPrefix(_client.CurrentUser, ref argPos) || message.HasCharPrefix(prefix, ref argPos))) 
            {
                return;
            }
           
            var context = new SocketCommandContext(_client, message);

            // execute command if one is found that matches
            await _commands.ExecuteAsync(context, argPos, _services); 
        }

        public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result)
        {
            // if a command isn't found, log that info to console and exit this method
            if (!command.IsSpecified)
            {
                System.Console.WriteLine($"Command failed to execute for [{context.User.Username}] <-> [{result.ErrorReason}]!");
                return;
            }
                

            // log success to the console and exit this method
            if (result.IsSuccess)
            {
                System.Console.WriteLine($"Command [{command.Value.Name}] executed for -> [{context.User.Username}]");
                return;
            }
                

            // failure scenario, let's let the user know
            await context.Channel.SendMessageAsync($"Sorry, {context.User.Username}... something went wrong -> [{result}]!");
        }        
    }
}

[top]

Adding Command Prefix to config.json

The above code will parse out the command prefix from the configuration file config.json.

Remember :

  • When debugging you want the config.json file in /bin/Debug/netcoreapp2.x/

  • When running the bot normally via dotnet projectname.dll, you want the config.json file in the root folder (same folder as the .dll/executable)

To add the prefix, your config.json should look like this:

{
    "Token":  "ThIsIsNtMyToKeN",
    "Prefix": ";"
}

You can replace the “;” with whatever one-character prefix you’d like to use.

Now what we have created our CommandHandler service, let’s wire up Program.cs to enable dependency injection and use it.

[top]

Adding Dependency Injection

Next we’ll want to modify Program.cs to add dependency injection.

What we need to do in Program.cs:

  • Add a using statement to gain access to the service we created
    - For this example that’s using chsarpi.Services;

  • Create a method that will construct the dependency injection model / ServicesProvider for the model we can consume later (ConfigureServices)

  • Call upon and use the ConfigureServices method, and work with the bot code via dependency injection

Below is the sample code to achieve what I’ve gone over above:

You can always view the most updated code, here: https://github.com/gngrninja/csharpi/blob/02-command-basics/Program.cs

using System;
using Discord;
using Discord.Net;
using Discord.Commands;
using Discord.WebSocket;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using csharpi.Services;

namespace csharpi
{
    class Program
    {
        // setup our fields we assign later
        private readonly IConfiguration _config;
        private DiscordSocketClient _client;

        static void Main(string[] args)
        {
            new Program().MainAsync().GetAwaiter().GetResult();
        }

        public Program()
        {
            // create the configuration
            var _builder = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile(path: "config.json");  

            // build the configuration and assign to _config          
            _config = _builder.Build();
        }

        public async Task MainAsync()
        {
            // call ConfigureServices to create the ServiceCollection/Provider for passing around the services
            using (var services = ConfigureServices())
            {
                // get the client and assign to client 
                // you get the services via GetRequiredService<T>
                var client = services.GetRequiredService<DiscordSocketClient>();
                _client = client;

                // setup logging and the ready event
                client.Log += LogAsync;
                client.Ready += ReadyAsync;
                services.GetRequiredService<CommandService>().Log += LogAsync;

                // this is where we get the Token value from the configuration file, and start the bot
                await client.LoginAsync(TokenType.Bot, _config["Token"]);
                await client.StartAsync();

                // we get the CommandHandler class here and call the InitializeAsync method to start things up for the CommandHandler service
                await services.GetRequiredService<CommandHandler>().InitializeAsync();

                await Task.Delay(-1);
            }
        }

        private Task LogAsync(LogMessage log)
        {
            Console.WriteLine(log.ToString());
            return Task.CompletedTask;
        }

        private Task ReadyAsync()
        {
            Console.WriteLine($"Connected as -> [{_client.CurrentUser}] :)");
            return Task.CompletedTask;
        }

        // this method handles the ServiceCollection creation/configuration, and builds out the service provider we can call on later
        private ServiceProvider ConfigureServices()
        {
            // this returns a ServiceProvider that is used later to call for those services
            // we can add types we have access to here, hence adding the new using statement:
            // using csharpi.Services;
            // the config we build is also added, which comes in handy for setting the command prefix!
            return new ServiceCollection()
                .AddSingleton(_config)
                .AddSingleton<DiscordSocketClient>()
                .AddSingleton<CommandService>()
                .AddSingleton<CommandHandler>()
                .BuildServiceProvider();
        }
    }
}

Now that we’re wired up to use dependency injection, and have our service created to handle commands, let’s create our first set of commands (the right way!).

[top]

Writing Robust Commands

In this section we will be writing our first real, robust commands. Writing commands this way gives us access to Discord.Net’s command writing goodness.

1. Create a folder named Modules, and under that folder a file named ExampleCommands.cs.

module_folder.png
example_commands_file.png

ExampleCommands will contain our first commands using this framework!

Below I have some code that will get us started with two commands:

You can always find the most up to date code, here: https://github.com/gngrninja/csharpi/blob/02-command-basics/Modules/ExampleCommands.cs

using Discord;
using Discord.Net;
using Discord.WebSocket;
using Discord.Commands;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

namespace csharpi.Modules
{
    // for commands to be available, and have the Context passed to them, we must inherit ModuleBase
    public class ExampleCommands : ModuleBase
    {
        [Command("hello")]
        public async Task HelloCommand()
        {
            // initialize empty string builder for reply
            var sb = new StringBuilder();

            // get user info from the Context
            var user = Context.User;
            
            // build out the reply
            sb.AppendLine($"You are -> [{user.Username}]");
            sb.AppendLine("I must now say, World!");

            // send simple string reply
            await ReplyAsync(sb.ToString());
        }

        [Command("8ball")]
        [Alias("ask")]
        [RequireUserPermission(GuildPermission.KickMembers)]
        public async Task AskEightBall([Remainder]string args = null)
        {
            // I like using StringBuilder to build out the reply
            var sb = new StringBuilder();
            // let's use an embed for this one!
            var embed = new EmbedBuilder();

            // now to create a list of possible replies
            var replies = new List<string>();

            // add our possible replies
            replies.Add("yes");
            replies.Add("no");
            replies.Add("maybe");
            replies.Add("hazzzzy....");

            // time to add some options to the embed (like color and title)
            embed.WithColor(new Color(0, 255,0));
            embed.Title = "Welcome to the 8-ball!";
            
            // we can get lots of information from the Context that is passed into the commands
            // here I'm setting up the preface with the user's name and a comma
            sb.AppendLine($"{Context.User.Username},");
            sb.AppendLine();

            // let's make sure the supplied question isn't null 
            if (args == null)
            {
                // if no question is asked (args are null), reply with the below text
                sb.AppendLine("Sorry, can't answer a question you didn't ask!");
            }
            else 
            {
                // if we have a question, let's give an answer!
                // get a random number to index our list with (arrays start at zero so we subtract 1 from the count)
                var answer = replies[new Random().Next(replies.Count - 1)];
                
                // build out our reply with the handy StringBuilder
                sb.AppendLine($"You asked: [**{args}**]...");
                sb.AppendLine();
                sb.AppendLine($"...your answer is [**{answer}**]");

                // bonus - let's switch out the reply and change the color based on it
                switch (answer) 
                {
                    case "yes":
                    {
                        embed.WithColor(new Color(0, 255, 0));
                        break;
                    }
                    case "no":
                    {
                        embed.WithColor(new Color(255, 0, 0));
                        break;
                    }
                    case "maybe":
                    {
                        embed.WithColor(new Color(255,255,0));
                        break;
                    }
                    case "hazzzzy....":
                    {
                        embed.WithColor(new Color(255,0,255));
                        break;
                    }
                }
            }

            // now we can assign the description of the embed to the contents of the StringBuilder we created
            embed.Description = sb.ToString();

            // this will reply with the embed
            await ReplyAsync(null, false, embed.Build());
        }
    }
}

That’s it! Now we’re ready to debug/test to see if it is all working!

Remember it is F5 in VS Code, or you can use [Debug] -> [Start Debugging] from the menu bar.

Hello Command

This command is a simple example of command execution. The command prefix I chose is “;”, and I’m debugging, so let’s see what happens in the server:

hello_command.png
hello_executed.png

Behind the scenes

The first thing we do when creating commands in a module file is to add the [Command] attribute. For the above command the syntax is:

[Command("hello")]

The text in quotes is what the command name is and what the user will use to trigger the method.
There are other attributes that can be added, more on those in the 8-ball example.

Now the method that performs the action can be declared:

        public async Task HelloCommand()
        {
            // initialize empty string builder for reply
            var sb = new StringBuilder();

            // get user info from the Context
            var user = Context.User;
            
            // build out the reply
            sb.AppendLine($"You are -> [{user.Username}]");
            sb.AppendLine("I must now say, World!");

            // send simple string reply
            await ReplyAsync(sb.ToString());
        }

The code above is what executes when the bot code finds a match for the “hello” command.

8-Ball Command

Now let’s move on to the 8-ball command. I’m debugging and ready to ask the 8-ball a question…

willthiswork.png
executed.png

Notice that with this command I was able to use ask, and not 8ball to trigger it. 8ball would also work, but with the attribute [Alias] we’re able to add an alias for the command. Here are the attributes we have setup for the 8ball command:

        [Command("8ball")]
        [Alias("ask")]
        [RequireUserPermission(GuildPermission.KickMembers)]

Hmm what’s this one all about? ([RequireUserPermission(GuildPermission.KickMembers)]). Let’s try to use the 8ball as a normal user on the server that cannot kick people:

needkick.png

The command framework allows you to specify the minimum permission needed to use the command. To remove that requirement, simply remove the attribute decorator:

[RequireUserPermission(GuildPermission.KickMembers)]

Behind the scenes

In the 8ball command we get a little more advanced by:

  • Requiring a minimum permission level

  • Adding a command alias

  • Replying with an embed

        public async Task AskEightBall([Remainder]string args = null)
        {
            // I like using StringBuilder to build out the reply
            var sb = new StringBuilder();
            // let's use an embed for this one!
            var embed = new EmbedBuilder();

            // now to create a list of possible replies
            var replies = new List<string>();

            // add our possible replies
            replies.Add("yes");
            replies.Add("no");
            replies.Add("maybe");
            replies.Add("hazzzzy....");

            // time to add some options to the embed (like color and title)
            embed.WithColor(new Color(0, 255,0));
            embed.Title = "Welcome to the 8-ball!";
            
            // we can get lots of information from the Context that is passed into the commands
            // here I'm setting up the preface with the user's name and a comma
            sb.AppendLine($"{Context.User.Username},");
            sb.AppendLine();

            // let's make sure the supplied question isn't null 
            if (args == null)
            {
                // if no question is asked (args are null), reply with the below text
                sb.AppendLine("Sorry, can't answer a question you didn't ask!");
            }
            else 
            {
                // if we have a question, let's give an answer!
                // get a random number to index our list with (arrays start at zero so we subtract 1 from the count)
                var answer = replies[new Random().Next(replies.Count - 1)];
                
                // build out our reply with the handy StringBuilder
                sb.AppendLine($"You asked: [**{args}**]...");
                sb.AppendLine();
                sb.AppendLine($"...your answer is [**{answer}**]");

                // bonus - let's switch out the reply and change the color based on it
                switch (answer) 
                {
                    case "yes":
                    {
                        embed.WithColor(new Color(0, 255, 0));
                        break;
                    }
                    case "no":
                    {
                        embed.WithColor(new Color(255, 0, 0));
                        break;
                    }
                    case "maybe":
                    {
                        embed.WithColor(new Color(255,255,0));
                        break;
                    }
                    case "hazzzzy....":
                    {
                        embed.WithColor(new Color(255,0,255));
                        break;
                    }
                }
            }

            // now we can assign the description of the embed to the contents of the StringBuilder we created
            embed.Description = sb.ToString();

            // this will reply with the embed
            await ReplyAsync(null, false, embed.Build());
        }

[top]

Getting it Working on the Pi

To get this working on our Raspberry Pi we will simply need to push the updated code to the Github repo, and pull it down to the Pi. We will need to update our config.json on the Pi, and copy it to the debug folder, as well as the bot folder (after we’ve added the Prefix line). If you’d like more information on getting the initial setup done with the Raspberry Pi, visit the below post and check out it’s prerequisites as well!

The below steps all assume we are sshed into our Pi.

ssh_in_pi.png

Switching Branches on Git

If you are following along, and want to use my Github repo as reference, you must ensure you’re working with the proper branch for this post.

First you’ll want to get into the directory:

cd csharpi

Then, you want to run:

git checkout 02-command-basics
git pull
You can see here we are in the intro branch, and we need to switch it up!

You can see here we are in the intro branch, and we need to switch it up!

checkpull.png

Pi config.json Editing

Now that we have the latest code on the Pi (refresher here if you’re using your own repo), let’s edit the config.json file to add the command prefix we want to use.

You’ll want to be in the cloned git repo’s directory, and assuming you have the base config created there from the linked post above, run (if you don’t, create it by using touch config.json):

nano config.json
nanotoeditconfig.png

In this file we want to ensure the Token and Prefix are there as such:

{
    "Token":  "ThIsIsNtMyToKeN",
    "Prefix": ";"
}
pi_config.png

Use the following sequence to save the contents:

CTRL+O
[Enter]
CTRL+X

Now let’s copy that to the debug folder so we can test/debug the code, and to the bot’s published location:

cp config.json bin/Debug/netcoreapp2.2/
cp config.json ~/bot
copyconfig.png

Let’s test the code from the Git repo…

Test Code From Repo

Now that we copied the new config.json file over to the Debug folder, we can test things out. Ensure you’re in the repo’s folder and run (remember, this method of running the bot takes a while to start):

dotnet run
piruns.png

Now to run a command in Discord just to be sure…

worksonpi.png
piexecuted.png

Success! Now to get it published and run it properly.

Running Published Code on Pi

Let’s get things published and running smoothly! To start out, ensure you are in the git repo’s folder and run:

dotnet publish -o ~/bot

Then you’ll want to get into the bot’s folder:

cd ~/bot
publishcd.png

And finally, you can run:

dotnet csharpi.dll
dotnet csharpi.png

Looks good, but one more test with Discord to really be sure!

8ballpub.png
pubrunning.png

Looks like we’re all set.

accuracy of 8-ball is not guaranteed

[top]

Conclusion

In this post we added some proper command handling to our Discord bot.
Feel free to change things around and see what you can make happen (or break and fix… break + fix = learn, right?).

In the next part of this series, we will add proper logging to the bot.
If you have any questions or comments, leave them below!

C# Discord Bot on Raspberry Pi: Simple Bot With Config File

C# Discord Bot on Raspberry Pi: Simple Bot With Config File

CSharPi: Simple Bot With Config File

RPi-Logo-Reg-SCREEN.png

Now let’s get a Discord bot working on our Pi!

Creating the Discord App

We will need a bot token to authenticate the bot and get things going.

1. Login to the developer portal for Discord, here: https://discordapp.com/developers/applications/.

2. Once logged in, click “New Application”, and give it a name.

newapp.png

3. Now click “Bot” on the menu to the left.

Click bot.png

4. Click “Add Bot”, and confirm.

Add Bot.png
botconfirm.png

5. Now you should see an option to reveal the bot’s token. We will need to get this information shortly.

token.png

6. Let’s invite the bot to a Discord server. You will need to be an administrator on the server you want to invite the bot to. To do this, go to the “General Information” option on the menu to the left, and copy the Client ID.

clientid.png

My bot’s client ID is 554549670953615380.

7. Go to https://discordapi.com/permissions.html#3198016, and input your client ID towards the bottom. I’ve added the basic permissions that will be good for this example bot. Feel free to add more if you’d like.

invitepage.png

8. The invite URL is the one you see towards the bottom. Go to that link in your browser. For my bot, it is: https://discordapp.com/oauth2/authorize?client_id=554549670953615380&scope=bot&permissions=3198016.

9. Confirm the server you are adding the bot to, and click “Authorize”.

confirmauth.png

10. You should now see an (offline) bot join your server.

joined.png

Token in Configuration File

Local Machine

From here on out we will be relying upon the groundwork we laid in this post:

If you haven’t done the setup in that post, but are an advanced user familiar with C# / .NET Core, carry on. Otherwise, I strongly recommend going through it.

1. Open the csharpi folder in Visual Studio Code on your local machine.

2. Create a new file in the root named config.json

createfile.png

3. Give it the following content:

    {
        "Token":  ""
    }

4. Move the file to the /bin/Debug/netcoreapp2.2 folder
(note) the only reason we are creating this copy is for testing, and when we debug the app it uses this folder as its root folder. We will be creating a separate config.json on the Pi in a moment.

movefile.png
movedfiled.png

5. Navigate back to https://discordapp.com/developers/applications/, and click the application we made earlier.

6. Click “Bot from the menu on the left, and then click “Reveal Token

bottokencopy.png

7. Copy the token and drop it inbetween the quotes in the config.json file.

droptokeninconffil.png

(note) it is very important you keep your token a secret, and regenerate it if you think anyone else knows it.

Raspberry Pi

1. SSH into your Raspberry Pi

2. Navigate to the published folder we created in part one:

        cd /home/pi/bot

3. Create the configuration file here:

        touch config.json

4. Copy the contents of the file from the one created on your local machine.

copycontents.png

5. Open the config.json file in /home/pi/bot on the Pi in nano:

        nano config.json
cdtouchnano.png

6. Once the editor is opened, paste in the contents.

pastenano.png

7. Use the following sequence to write the contents and exit the editor:

        CTRL+O
        [Enter]
        CTRL+X

8. Verify the file contents via the more command:

        more config.json
Screen Shot 2019-03-11 at 12.18.17 AM.png

Add Required Packages

Now it is time to add required packages to our project. We will need to add the Discord.Net package, and a couple Microsoft configuration packages to help us read and store the configuration file.

1. Open your favorite console / terminal, and navigate to the csharpi project folder on your local machine.

2. Run the following commands:

dotnet add package Discord.Net --version 2.0.1
dotnet add package Microsoft.Extensions.Configuration --version 3.0.0-preview3.19153.1   
dotnet add package Microsoft.Extensions.Configuration.Json --version 3.0.0-preview3.19153.1

Add Simple Bot Code

Now we’re going to replace the contents of Program.cs with the simple bot code. This code will be the basis for future posts to come.

1. Open the csharpi folder on your local machine in Visual Studio Code.

2. Replace the contents of Program.cs with the following:
(note) If you did not name your project csharpi, be mindful of the namespace declaration and update yours to match your project name!

using System;
using Discord;
using Discord.Net;
using Discord.Commands;
using Discord.WebSocket;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace csharpi
{
    class Program
    {
        private readonly DiscordSocketClient _client;
        private readonly IConfiguration _config;

        static void Main(string[] args)
        {
            new Program().MainAsync().GetAwaiter().GetResult();
        }

        public Program()
        {
            _client = new DiscordSocketClient();

            //Hook into log event and write it out to the console
            _client.Log += LogAsync;

            //Hook into the client ready event
            _client.Ready += ReadyAsync;

            //Hook into the message received event, this is how we handle the hello world example
            _client.MessageReceived += MessageReceivedAsync;

            //Create the configuration
            var _builder = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile(path: "config.json");            
            _config = _builder.Build();
        }

        public async Task MainAsync()
        {
            //This is where we get the Token value from the configuration file
            await _client.LoginAsync(TokenType.Bot, _config["Token"]);
            await _client.StartAsync();

            // Block the program until it is closed.
            await Task.Delay(-1);
        }

        private Task LogAsync(LogMessage log)
        {
            Console.WriteLine(log.ToString());
            return Task.CompletedTask;
        }

        private Task ReadyAsync()
        {
            Console.WriteLine($"Connected as -> [{_client.CurrentUser}] :)");
            return Task.CompletedTask;
        }

        //I wonder if there's a better way to handle commands (spoiler: there is :))
        private async Task MessageReceivedAsync(SocketMessage message)
        {
            //This ensures we don't loop things by responding to ourselves (as the bot)
            if (message.Author.Id == _client.CurrentUser.Id)
                return;

            if (message.Content == ".hello")
            {
                await message.Channel.SendMessageAsync("world!");
            }  
        }
    }
}
programcseditor.png

3. Now that we have the new code for Program.cs, let’s test it by debugging (F5).

debugconnected.png

4. Now that we verified it is connected and debugging, try sending .hello to the discord server your bot is in, and see what happens (the bot should appear online now!)

hellofromlocal.png

Awesome! It works locally. Now let’s work on getting it to the Pi.

Commit New Code to Github

Let’s get our new code committed to Github!

1. Open up the csharpi folder on your local machine with VS Code.

2. The Git icon should show that 2 files have been modified. These are the .csproj file with the required packages we added, and the Program.cs file we modified.

gitmod.png

3. Since VS Code is Git-aware, let’s push these changes up to our Github repository.

4. Add a commit message and click the checkmark icon to stage/commit the files.

commitmessage.png

5. Now click the three dots “”, and select “Push”.

6. Verify the files have been modified by going to your Github repository’s page:

Update Code on Pi

Now let’s get the updated code on the Raspberry Pi, and publish the app again. After we do that, we can run the updated app and ensure it works as the Discord bot!

1. SSH into your Pi.

2. Navigate to the source code folder /home/pi/csharpi:

    cd /home/pi/csharpi

3. Run:

    git pull

(you should see that some changes have been made)

gitpull.png

4. Now let’s publish and run the application (since we already created config.json in /home/pi/bot earlier, it should pick up that file and have the token ready). The first command publishes the application, then we change directory to the published application, and run it.

dotnet publish -o /home/pi/bot
cd /home/pi/bot
dotnet csharpi.dll

(note) The restore/publishing process will take a while, but you only need to run it if your code has changed.

If all goes well, we should see our bot connected message, and we can test it out in the Discord server!

itworks.png
helloworldpi.png

And there you have it, a Discord bot running on a Raspberry Pi, with .NET Core and C#. The next parts of this series will go over proper command handling (hint: not how we did it here), and more!

C# Discord Bot Series

Next post:

If you have any feedback or questions, feel free to leave a comment below!