Friday, 2 October 2020

Discord Bots!

So you are a developer that wants to build a Discord bot and you don't know where to start, right? Well, you are in the right place, follow this guide and learn the basics of the Discord API.

Discord does not provide wrappers for it's API so instead it advertises some 3rd Party ones. Developers have created lots of wrappers but the one we are using is Discord.NET. Discord.NET is a 3rd party Discord API wrapper for C#. It is not the only one as there is also DSharp+. The most common library is discord.js. It is widely used for developing bots because it is the easiest to use. Discord.NET doesn't have much documentation but it has a very efficient logger service that will say exactly what happens when a command is executed.

Onto the code now!

The first thing that you need to do is create a C# console project. Name the project with the name of your bot. Once you got that ready, head over to the Discord Developer Portal and create an application. Name the application with the same name as your console project. Once the application is created head over to the "Bots" tab and click create bot. Note: The action is irreversible, once you create a bot you cannot delete it

Once the bot is created don't close the page but head over to your code. You should have 2 classes already created: Startup.cs and Program.cs. Head over to the NuGet package manager and install these packages:


Once you got them installed proceed to to Program.cs and type the following:
using System;
using System.Threading.Tasks;
using Discord.Net;

    namespace YourProjectName
    { 
        class Program
        { 

        public static async Task Main(string[] args) 
        => await Startup.RunAsync(args); 

This class will return an error saying that it cannot find the RunAsync method. We will create this method in the Startup.cs class. Now switch to the Startup.cs class and type the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text; using System.Threading.Tasks;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Brobot.Services;

namespace Brobot
    public class Startup
    
    public IConfigurationRoot configuration { get; } 
    
    public Startup(string[] args)
    
        var builder = new ConfigurationBuilder()
                              .SetBasePath(AppContext.BaseDirectory)
                              .AddYamlFile("_config.yml");
       
        configuration = builder.Build();
    } 

   public static async Task RunAsync(string[] args)
  
       var startup = new Startup(args);
       await startup.RunAsync();

   } 

   public async Task RunAsync()
   {
      var services = new ServiceCollection();
      ConfigureServices(services);
      
     var provider = services.BuildServiceProvider();
     provider.GetRequiredService(); 
     var logging = provider.GetRequiredService();
     await provider.GetRequiredService().StartAsync();
     await Task.Delay(-1); 

  } 

 private void ConfigureServices(ServiceCollection services)
 { 

     services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig 
         { 
             LogLevel = Discord.LogSeverity.Verbose, 
             MessageCacheSize = 1000 
         })) 
             .AddSingleton(new CommandService(new CommandServiceConfig 
         {
             LogLevel = Discord.LogSeverity.Verbose, 
             DefaultRunMode = RunMode.Async, 
             CaseSensitiveCommands = false 
         })) 
             .AddSingleton<CommandHandler>() 
             .AddSingleton<LoggerService() 
             .AddSingleton<StartupService>() 
             .AddSingleton(configuration); 
  } 
 }
}

This is basically the class that initialises the basics of the bot and how it is supposed to work. This class will also start up the Logger Service, Command Handler and the Startup service. 
Now create a new folder and call it Services. Add a class StartupService.cs and type the following code

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

namespace Brobot.Services
{
    public class StartupService
    {
        public static IServiceProvider _provider;
        private readonly DiscordSocketClient _discord;
        private readonly CommandService _commands;
        private readonly IConfigurationRoot _config;
        public StartupService(IServiceProvider provider, DiscordSocketClient discord, CommandService commands, IConfigurationRoot config)
        {
            _provider = provider;
            _discord = discord;
            _config = config;
            _commands = commands;
        }

        public async Task StartAsync()
        {
            string token = _config["token:discord"];
            
            if (string.IsNullOrEmpty(token))
            {
                Console.WriteLine("Provide the Discord token in _config.yml. If the file is not present, make it!");
                return;
            }

            await _discord.LoginAsync(TokenType.Bot, token);
            await _discord.StartAsync();
            await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _provider);
        }

    }
}

This class will require you to set up a config.yml file. In this file you will paste the bot's token and prefix

prefix: 'bot's prefix!'
token:
    discord: 'the token goes here'


now you're all set. the bot will start but not do anything.
In the service folder add a new class and call it CommandHandler.cs

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

namespace Brobot.Services
{
    public class CommandHandler
    {
        public static IServiceProvider _provider;
        public static DiscordSocketClient _discord;
        public static CommandService _commands;
        public static IConfigurationRoot _config;

        public CommandHandler(DiscordSocketClient discord, CommandService commands, IConfigurationRoot config, IServiceProvider provider)
        {
            _provider = provider;
            _discord = discord;
            _config = config;
            _commands = commands;

            _discord.Ready += OnReady;
            _discord.MessageReceived += OnMessageReceived;

        }

        private async Task OnMessageReceived(SocketMessage arg)
        {
            var msg = arg as SocketUserMessage;
            if (msg.Author.IsBot) return;

            var context = new SocketCommandContext(_discord, msg);

            int pos = 0;
            //msg.HasStringPrefix(_config["prefix"], ref pos) ||  just for the prefix
            if (msg.HasStringPrefix(_config["prefix"], ref pos) || msg.HasMentionPrefix(_discord.CurrentUser, ref pos))
            {
                var result = await _commands.ExecuteAsync(context, pos, _provider);

                if (!result.IsSuccess)
                {
                    var reason = result.Error;
                    Console.WriteLine("There has been an error!");
                    Console.WriteLine(reason);
                    Console.WriteLine(result);

                    await context.Channel.SendMessageAsync($"There has been an error in executing your command. Here's the error: \n ```{result}``` \n Please contact if you encounter further problems." +
                        $"\n Run  for error definitions");//error message

                    
                }
            }
        }

        public Task OnReady()
        {
            _discord.SetGameAsync("Version 1.1.0");
            Console.WriteLine("Brobot is up and running!");//login message
            Console.WriteLine($"Connected as: {_discord.CurrentUser.Username}#{_discord.CurrentUser.Discriminator}");
            return Task.CompletedTask;
        }
    }
}

This class will handle all the commands and post errors in the console and in the discord channels. You can change the error message and the Login message and the Activity to whatever you want!

Now in the same folder make a class and call it LoggerService.cs
Type the following code
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;

public class LoggingService
{
public LoggingService(DiscordSocketClient client, CommandService command)
{
client.Log += LogAsync;
command.Log += LogAsync;
}
private Task LogAsync(LogMessage message)
{
if (message.Exception is CommandException cmdException)
{
Console.WriteLine($"[Command/{message.Severity}] {cmdException.Command.Aliases.First()}"
+ $" failed to execute in {cmdException.Context.Channel}.");
Console.WriteLine(cmdException);
}
else
Console.WriteLine($"[General/{message.Severity}] {message}");

return Task.CompletedTask;
}
}

This will create a logger that will post in the console when the bot connects, ping time, where the bot connects and any errors.

The bot is ready to run but it is missing the commands. Create a Commands folder in the main directory of the project and add a class. Name it whatever you want
The process is equal for most commands:

1. Make the main class public and inherit ModuleBase (just add " : ModuleBase" at the end of the class declaration
2. Create an async Task
3. add the command content and action!



Congratulations! you have built your first Discord bot in C#

No comments:

Post a Comment

Technologies for Websites

 Making websites from scratch isn't easy, you need to use specific technologies and protect yourself from common Internet Crimes, such a...