2
\$\begingroup\$

I am no longer running this server, nor does this question reflect my current political views.

I am currently running a small Donald Trump-themed Discord server. Recently, a few members requested that I create a bot for the server, so I obliged and created, this, TrumpBot. It uses the Discord.Net API to interface with Discord and supports a small number of commands.

TrumpBotMain.cs

using System;
using Discord;
using Discord.API;
using Discord.ETF;
using Discord.Net;
using Discord.Legacy;
using Discord.Logging;
using Discord.Commands;

namespace TrumpBot
{
    public class TrumpBotMain
    {
        public static void Main(string[] args)
        {
            ClientManager clientManager = new ClientManager();
            clientManager.Start();
        }
    }
}

CommandManager.cs

using System;
using Discord;
using Discord.API;
using Discord.ETF;
using Discord.Net;
using Discord.Legacy;
using Discord.Logging;
using Discord.Commands;

namespace TrumpBot
{
    /// <summary>
    /// This class is responsible for managing and creating bot commands that users
    /// in a server can use to control the bot.
    /// </summary>
    public class CommandManager
    {
        public CommandService CommandService { get; set; }
        public DiscordClient DiscordClient { get; set; }

        /// <summary>
        /// Constructor for the CommandManager class.
        /// </summary>
        /// <param name="discordClient">The current discord client.</param>
        public CommandManager(DiscordClient discordClient)
        {
            this.CommandService = discordClient.GetService<CommandService>();
            this.DiscordClient = discordClient;
        }

        /// <summary>
        /// Register a command to the Discord client.
        /// </summary>
        /// <param name="commandName">The command's name.</param>
        /// <param name="commandDescription">The command's description.</param>
        /// <param name="commandAction">The command's action.</param>
        public void RegisterCommand(string commandName, string commandDescription, Action<CommandEventArgs> commandAction)
        {
            Console.WriteLine($"[INTERNAL] Registered command '{commandName}' to the client.");
            this.CommandService.CreateCommand(commandName)
                .Description(commandDescription)
                .Do(e =>
            {
                commandAction(e);
                Console.WriteLine($"[INTERNAL] Command '{commandName}' executed by '{e.User.Name}'.");
            });
        }

        /// <summary>
        /// Register a command to the Discord client with one parameter.
        /// </summary>
        /// <param name="commandName">The command's name.</param>
        /// <param name="commandDescription">The command's description.</param>
        /// <param name="parameterOneName">The first parameter's name.</param>
        /// <param name="parameterOneType">The first parameter's type.</param>
        /// <param name="commandAction">The command's action.</param>
        public void RegisterCommand(
            string commandName, 
            string commandDescription, 
            string parameterOneName, 
            ParameterType parameterOneType, 
            Action<CommandEventArgs> commandAction
        )
        {
            Console.WriteLine($"[INTERNAL] Registered command '{commandName}' to the client.");
            this.CommandService.CreateCommand(commandName)
                .Description(commandDescription)
                .Parameter(parameterOneName, parameterOneType)
                .Do(e =>
            {
                commandAction(e);
                Console.WriteLine($"[INTERNAL] Command '{commandName}' executed by '{e.User.Name}'");
            });
        }

        /// <summary>
        /// Register a command to the Discord client with two parameters.
        /// </summary>
        /// <param name="commandName">The command's name.</param>
        /// <param name="commandDescription">The command's description.</param>
        /// <param name="parameterOneName">The first parameter's name.</param>
        /// <param name="parameterOneType">The first parameter's type.</param>
        /// <param name="parameterTwoName">The second parameter's name.</param>
        /// <param name="parameterTwoType">The second parameter's type.</param>
        /// <param name="commandAction">The command's action.</param>
        public void RegisterCommand(
            string commandName,
            string commandDescription,
            string parameterOneName,
            ParameterType parameterOneType,
            string parameterTwoName,
            ParameterType parameterTwoType,
            Action<CommandEventArgs> commandAction
        )
        {
            Console.WriteLine($"[INTERNAL] Registered command '{commandName}' to the client.");
            this.CommandService.CreateCommand(commandName)
                .Description(commandDescription)
                .Parameter(parameterOneName, parameterOneType)
                .Parameter(parameterTwoName, parameterTwoType)
                .Do(e =>
            {
                commandAction(e);
                Console.WriteLine($"[INTERNAL] Command '{commandName}' executed by '{e.User.Name}'");
            });
        }

        /// <summary>
        /// Register a command to the Discord client with three parameters.
        /// </summary>
        /// <param name="commandName">The command's name.</param>
        /// <param name="commandDescription">The command's description.</param>
        /// <param name="parameterOneName">The first parameter's name.</param>
        /// <param name="parameterOneType">The first parameter's type.</param>
        /// <param name="parameterTwoName">The second parameter's name.</param>
        /// <param name="parameterTwoType">The second parameter's type.</param>
        /// <param name="parameterThreeName">The third parameter's name.</param>
        /// <param name="parameterThreeType">The third parameter's type.</param>
        /// <param name="commandAction">The command's action.</param>
        public void RegisterCommand(
            string commandName,
            string commandDescription,
            string parameterOneName,
            ParameterType parameterOneType,
            string parameterTwoName,
            ParameterType parameterTwoType,
            string parameterThreeName,
            ParameterType parameterThreeType,
            Action<CommandEventArgs> commandAction
        )
        {
            Console.WriteLine($"[INTERNAL] Registered command '{commandName}' to the client.");
            this.CommandService.CreateCommand(commandName)
                .Description(commandDescription)
                .Parameter(parameterOneName, parameterOneType)
                .Parameter(parameterTwoName, parameterTwoType)
                .Parameter(parameterThreeName, parameterThreeType)
                .Do(e =>
            {
                commandAction(e);
                Console.WriteLine($"[INTERNAL] Command '{commandName}' executed by '{e.User.Name}'");
            });
        }
    }
}

ClientManager.cs

using System;
using System.IO;
using System.Threading;
using Discord;
using Discord.API;
using Discord.ETF;
using Discord.Net;
using Discord.Legacy;
using Discord.Logging;
using Discord.Commands;

namespace TrumpBot
{
    /// <summary>
    /// This class is responsible for storing and managing a discord client, as
    /// well as logging and managing events.
    /// </summary>
    public class ClientManager
    {
        public static string APP_NAME = "TrumpBot";
        public static string APP_VERSION = "1.0.0.0";
        public static string APP_TOKEN = "[redacted]";
        public static char COMMAND_PREFIX = '!';

        public DiscordClient DiscordClient { get; set; }
        public CommandManager CommandManager { get; set; }

        /// <summary>
        /// Constructor for the ClientManager class.
        /// </summary>
        public ClientManager()
        {
            Console.WriteLine($"[INTERNAL] Generating client configuration.");
            this.DiscordClient = new DiscordClient(configurationBuilder => 
            {
                configurationBuilder.AppName = APP_NAME;
                configurationBuilder.AppVersion = APP_VERSION;
                configurationBuilder.LogHandler = this.LogHandler;
            });

            Console.WriteLine($"[INTERNAL] Generating command configuration.");
            this.DiscordClient.UsingCommands(usingCommands =>
            {
                usingCommands.PrefixChar = COMMAND_PREFIX;
                usingCommands.AllowMentionPrefix = true;
                usingCommands.HelpMode = HelpMode.Public;
            });

            Console.WriteLine($"[INTERNAL] Registering commands.");
            this.CommandManager = new CommandManager(this.DiscordClient);
            this.CommandManager.RegisterCommand("alive", "Check to see if TrumpBot is alive.", this.CommandAlive);
            this.CommandManager.RegisterCommand("quit", "Force TrumpBot to quit.", this.CommandQuit);
            this.CommandManager.RegisterCommand("maga", "Use this command to Make America Great Again.", this.CommandMaga);
            this.CommandManager.RegisterCommand("farage", "Post a random picture of Nigel Farage.", this.CommandFarage);
            this.CommandManager.RegisterCommand("buildawall", "Build Trump's great wall.", this.CommandBuildAWall);
            this.CommandManager.RegisterCommand("ask", "Ask TrumpBot a question", "question", ParameterType.Unparsed, this.CommandAsk);
        }

        /// <summary>
        /// Start the Discord client.
        /// </summary>
        public void Start()
        {
            this.DiscordClient.ExecuteAndWait(async () => 
            {
                Console.WriteLine($"[INTERNAL] Connecting to the TRUMP server.");
                await this.DiscordClient.Connect(APP_TOKEN, TokenType.Bot);
            });
        }

        /// <summary>
        /// The 'alive' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandAlive(CommandEventArgs e)
        {
            e.Channel.SendMessage("TrumpBot is alive.");
        }

        /// <summary>
        /// The 'quit' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandQuit(CommandEventArgs e)
        {
            if(e.User.ServerPermissions.Administrator)
            {
                e.Channel.SendMessage("TrumpBot is quitting.");
                Thread.Sleep(1000);
                this.DiscordClient.Disconnect();
            }
            else
            {
                e.Channel.SendFile("Only users with the 'Administrator' privilege can quit TrumpBot.");
            }
        }

        /// <summary>
        /// The 'maga' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandMaga(CommandEventArgs e)
        {
            string[] possibleImageAssetNames = new string[]
            {
                @".\Assets\Trump\donald-1.jpg",
                @".\Assets\Trump\donald-2.jpg",
                @".\Assets\Trump\donald-3.jpg",
                @".\Assets\Trump\donald-4.jpg",
                @".\Assets\Trump\donald-5.jpg",
                @".\Assets\Trump\donald-6.jpg",
                @".\Assets\Trump\donald-7.jpg",
                @".\Assets\Trump\donald-8.jpg",
                @".\Assets\Trump\donald-9.jpg",
                @".\Assets\Trump\donald-10.jpg",
                @".\Assets\Trump\donald-11.jpg",
                @".\Assets\Trump\donald-12.jpg",
                @".\Assets\Trump\donald-13.jpg",
                @".\Assets\Trump\donald-14.jpg",
                @".\Assets\Trump\donald-15.jpg",
                @".\Assets\Trump\donald-16.jpg",
                @".\Assets\Trump\donald-17.jpg",
                @".\Assets\Trump\donald-18.jpg",
                @".\Assets\Trump\donald-19.jpg",
                @".\Assets\Trump\donald-20.jpg",
                @".\Assets\Trump\donald-21.jpg",
                @".\Assets\Trump\donald-22.jpg",
                @".\Assets\Trump\donald-23.jpg",
                @".\Assets\Trump\donald-24.jpg",
                @".\Assets\Trump\donald-25.jpg",
            };

            Random newRandom = new Random();
            string randomImageAssetName = possibleImageAssetNames[newRandom.Next(0, possibleImageAssetNames.Length - 1)];
            if(!File.Exists(randomImageAssetName))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"[INTERNAL] Invalid file path '{randomImageAssetName}'.");
                Console.ResetColor();
            }

            e.Channel.SendMessage("MAKE AMERICA GREAT AGAIN");
            e.Channel.SendFile(randomImageAssetName);
        }

        /// <summary>
        /// The 'farage' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandFarage(CommandEventArgs e)
        {
            string[] possibleImageAssetNames = new string[]
            {
                @".\Assets\Farage\farage-1.jpg",
                @".\Assets\Farage\farage-2.jpg",
                @".\Assets\Farage\farage-3.jpg",
                @".\Assets\Farage\farage-4.jpg",
                @".\Assets\Farage\farage-5.jpg",
                @".\Assets\Farage\farage-6.jpg",
                @".\Assets\Farage\farage-7.jpg",
                @".\Assets\Farage\farage-8.jpg",
                @".\Assets\Farage\farage-9.jpg",
                @".\Assets\Farage\farage-10.jpg",
                @".\Assets\Farage\farage-11.jpg",
                @".\Assets\Farage\farage-12.jpg",
                @".\Assets\Farage\farage-13.jpg",
                @".\Assets\Farage\farage-14.jpg",
            };

            Random newRandom = new Random();
            string randomImageAssetName = possibleImageAssetNames[newRandom.Next(0, possibleImageAssetNames.Length - 1)];
            if(!File.Exists(randomImageAssetName))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"[INTERNAL] Invalid file path '{randomImageAssetName}'.");
                Console.ResetColor();
            }

            e.Channel.SendMessage("CAN'T BARRAGE THE FARAGE");
            e.Channel.SendFile(randomImageAssetName);
        }

        /// <summary>
        /// The 'buildawall' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandBuildAWall(CommandEventArgs e)
        {
            e.Channel.SendMessage("I will build a great, great wall on our southern border and I will make Mexico pay for that wall.");
            e.Channel.SendFile(@".\Assets\Trump\trump-wall.jpg");
        }

        /// <summary>
        /// The 'ask' command.
        /// </summary>
        /// <param name="e">The command event arguments.</param>
        private void CommandAsk(CommandEventArgs e)
        {
            string[] possibleAnswers = new string[]
            {
                "It is certain.",
                "It is decidedly so.",
                "Without a doubt.",
                "Yes, definitely.",
                "You may rely on it.",
                "As I see it, yes.",
                "Most likely.",
                "Outlook good.",
                "Yes.",
                "Signs point to yes.",

                "Reply hazy try again.",
                "Ask again later.",
                "Better not tell you now.",
                "Cannot predict now.",
                "Concentrate and ask again.",

                "Don't count on it.",
                "My reply is no.",
                "My sources say no.",
                "Outlook not so good.",
                "Very doubtful."
            };

            Random newRandom = new Random();
            string selectedRandomAnswer = possibleAnswers[newRandom.Next(0, possibleAnswers.Length - 1)];

            e.Channel.SendMessage($"`{e.User.Name}` asked *'{e.GetArg("question")}'.*");
            e.Channel.SendMessage($"Answer: `{selectedRandomAnswer}`");
        }

        /// <summary>
        /// This function is responsible for logging every event that occurs in the server
        /// the bot is registered to.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="logMessageEventArgs">The event arguments.</param>
        private void LogHandler(object sender, LogMessageEventArgs logMessageEventArgs)
        {
            switch(logMessageEventArgs.Severity)
            {
                case LogSeverity.Error:
                    Console.ForegroundColor = ConsoleColor.Red;
                    break;

                case LogSeverity.Warning:
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    break;

                case LogSeverity.Info:
                    Console.ForegroundColor = ConsoleColor.Green;
                    break;

                default:
                    Console.ForegroundColor = ConsoleColor.White;
                    break;
            }

            Console.WriteLine($"[{logMessageEventArgs.Severity}] [{logMessageEventArgs.Source}] {logMessageEventArgs.Message}");
            Console.ResetColor();
        }
    }
}

What can I improve?

\$\endgroup\$
6
\$\begingroup\$
using System;
using Discord;
using Discord.API;
using Discord.ETF;
using Discord.Net;
using Discord.Legacy;
using Discord.Logging;
using Discord.Commands;

That's a lot of using statements for the tremendous amount of one. single. class. Remove using-statements that serve no purpose. Any IDE worth it's salt will mark them as unused (if you even expand them) and generally will offer a refactoring to remove the unused import / using / ... statements.


Console.WriteLine($"[INTERNAL] Registered command '{commandName}' to the client.");

This and related statements are actually logging. What if you wanted to audit the usages of commands by users? You'd have to get your console output into a logfile somehow. It'd be easier to abstract this through a proper logging library, and have that library write to console instead. Or to a file, as soon as you want files. It makes changing the logging mechanism into a single-line configuration change instead of a multi-hour endeavour.

Discord.NET even provides facilities to allow "proper" logging. You can even override how log-handling works. Actually you did that and log to console, but don't extend this further into using it for your own purposes.


The code also has multiple overloads for different parameter configurations. It would be much cleaner to have a signature like the following:

public void RegisterCommand(
    string commandName,
    string commandDescription,
    Parameter[] parameters,
    Action<CommandEventArgs> commandAction
)
{
    // ...

that would also remove a lot of duplication in the code here:

var command = CommandService.CreateCommand(commandName)
    .Description(commandDescription);
for (var param in parameters) 
{
    command = command.Parameter(param.Name, param.Type);
}
command.do(e =>
    {
        commandAction(e);
        // logging
    });
\$\endgroup\$
  • \$\begingroup\$ log4net allows capturing console write lines and putting them into a log file, FWIW. \$\endgroup\$ – user34073 May 16 '18 at 15:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.