rmsoft1 icon

Program.cs

rmsoft1 | PRO | 04/29/18 11:04:50 PM UTC | 0 ⭐ | 294 👁️ | Never ⏰ | []
C# |

58.01 KB

|

None

|

0 👍

/

0 👎

//https://github.com/RMSoftware-Development Commit soon.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using Discord;
using Discord.WebSocket;
using System.Reflection;
using Discord.Commands;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using RMSoftware.IO;
using System.Diagnostics;
using Newtonsoft.Json;
using System.Runtime.ExceptionServices;
 
namespace RMSoftware.ModularBot
{
    class Program
    {
        #region STATIC fields & properties
        public static DateTime StartTime;
        public static bool BCMDStarted = false;
        public static bool WizardDebug = false;
        public static bool discon = false;
        public static bool CriticalError = false;
        public static bool RestartRequested = false;
        public static Exception crashException = null;
        static string auth = "";
        static bool CRASH = false;
        public static string[] ARGS = null;
        public static CustomCommandManager ccmg { get; private set; }
        public static CmdRoleManager rolemgt { get; private set; }
        public static char CommandPrefix { get; private set; }
        public static DiscordSocketClient _client;
        static List<SocketMessage> messageQueue = new List<SocketMessage>();
        public static bool LOG_ONLY_MODE = false;//Set by args on start. if true, console header and resets are ignored. Should only be done if hosting the process in a UI.
        public static bool MessagesDisabled = false;
        public static ConsoleLogWriter writer = new ConsoleLogWriter();
        /// <summary>
        /// Application's main configuration file
        /// </summary>
        public static INIFile MainCFG { get; private set;}
        #endregion
 
        #region Instance fields & properties
        public IServiceCollection serviceCollection;
        public IServiceProvider services;
        public CommandService cmdsvr = new Discord.Commands.CommandService();
        #endregion
 
        #region STATIC methods
 
        public static int Main(string[] args)
        {
            ARGS = args;
            try
            {
                foreach (string item in args)
                {
                    if (item.ToLower() == "-log_only")
                    {
                        LOG_ONLY_MODE = true;
                    }
                    if (item.ToLower() == "-crashed")
                    {
                        CRASH = true;
                    }
                    if (item.StartsWith("-auth"))
                    {
                        auth = item.Split(' ')[1];
                    }
                }
                BCMDStarted = false;
                ConsoleGUIReset(ConsoleColor.Green, ConsoleColor.Black, "Welcome", 79, 45);
                Console.Title = "RMSoftware.ModularBOT";
                Console.WriteLine("Oh, Hello! Greetings! Salutations! Stuff is about to happen... Please wait...");
                System.Threading.Thread.Sleep(800);
                ConsoleWriteImage(Prog.res1.Resource1.RMSoftwareICO);
                System.Threading.Thread.Sleep(3000);
                ConsoleGUIReset(ConsoleColor.Cyan, ConsoleColor.Black, "Program Starting");
                ccmg = new CustomCommandManager();
                rolemgt = new CmdRoleManager();
 
                AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
                //AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
 
 
 
                SetupWizard();
 
                if (auth.Length == 0)
                {
                    ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkBlue, "Initializing");
                    Console.WriteLine("You didn't specify an authorization token as parameter....");
                    Console.WriteLine("usage: RMSoftwareModularBot.exe <authToken>");
                    Console.WriteLine("No big deal though, Will use configuration instead");
                    auth = Program.MainCFG.GetCategoryByName("Application").GetEntryByName("botToken").GetAsString();
                }
 
                //set command prefix
 
                CommandPrefix = Convert.ToChar(MainCFG.GetCategoryByName("Application").GetEntryByName("cmdPrefix").GetAsInteger());
                Program.LogToConsole(new LogMessage(LogSeverity.Info, "Initialize", $"Using the command prefix: {(char)CommandPrefix}"));
                var prg = new Program();
                prg.MainAsync(auth).GetAwaiter().GetResult();
                Task.Run(new Action(ReadConsole));
                Task.Run(new Action(writer.ProcessStack().GetAwaiter().GetResult));
                SpinWait.SpinUntil(BotShutdown);//Wait until shutdown;
                BCMDStarted = false;
                ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkRed, "Disconnected");
                if (CriticalError)
                {
                    Program.LogToConsole(new LogMessage(LogSeverity.Critical, "Session", "Failed to resume previous session.",crashException));
                    using (FileStream fs = File.Create("CRASH.LOG"))
                    {
                        using (StreamWriter sw = new StreamWriter(fs))
                        {
                            LogMessage m = new LogMessage(LogSeverity.Critical, "Session", crashException.Message, crashException);
                            sw.WriteLine(m.ToString());
                            List<string> restart_args = new List<string>();
                            restart_args.AddRange(ARGS);
                            if (!restart_args.Contains("-crashed"))
                            {
                                restart_args.Add("-crashed");
                            }
                            ARGS = restart_args.ToArray();
                            sw.Flush();
 
                        }
                    }
                    if (!LOG_ONLY_MODE)
                    {
                        Program.LogToConsole(new LogMessage(LogSeverity.Warning, "Application", "Restarting in 4 seconds..."));
                        Thread.Sleep(1000);
                    }
 
                    RestartRequested = true;
                    _client = null;
                }
            }
            catch (Exception ex)
            {
                crashException = ex;
                ConsoleGUIReset(ConsoleColor.White,ConsoleColor.DarkRed,"Critical Application Error.");
                Program.LogToConsole(new LogMessage(LogSeverity.Critical, "Exception", ex.Message, ex));
                CriticalError = true;
                RestartRequested = true;
                using (FileStream fs = File.Create("CRASH.LOG"))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        LogMessage m = new LogMessage(LogSeverity.Critical, "Session", ex.Message, ex);
                        sw.WriteLine(m.ToString());
                        List<string> restart_args = new List<string>();
                        restart_args.AddRange(ARGS);
                        if (!restart_args.Contains("-crashed"))
                        {
                            restart_args.Add("-crashed");
                        }
                        ARGS = restart_args.ToArray();
                        sw.Flush();
 
                    }
                }
                using (FileStream fs = new FileStream("ERRORS.LOG", FileMode.Append))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
 
                        sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy") + "   " + ex.ToString());
                        sw.Flush();
 
                    }
                }
            }
            if (RestartRequested)
            {
                Process p = new Process();
                string flattened = "";
                foreach (string item in ARGS)
                {
                    flattened += item + " ";
                }
                p.StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, flattened);
                ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkRed, "Disconnected");
                writer.WriteEntry(new LogMessage(LogSeverity.Warning, "Program", "Restarting in 3...")).GetAwaiter().GetResult();//using direct writer to try and prevent blanks.
                Thread.Sleep(1000);
                ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkRed, "Disconnected");
                writer.WriteEntry(new LogMessage(LogSeverity.Warning, "Program", "Restarting in 2...")).GetAwaiter().GetResult();
 
                Thread.Sleep(1000);
                ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkRed, "Disconnected");
                writer.WriteEntry(new LogMessage(LogSeverity.Warning, "Program", "Restarting in 1...")).GetAwaiter().GetResult();
 
                Thread.Sleep(1000);
                p.Start();
                return 0x00000c4;
            }
            writer.WriteEntry(new LogMessage(LogSeverity.Warning, "Program", "Termination.")).GetAwaiter().GetResult();
 
            if (!CriticalError)
            {
 
                return 4007;//NOT OKAY
            }
            else
            {
 
                return 200;//OK status
            }
        }
 
        private static void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
        {
            writer.push(new LogMessage(LogSeverity.Critical, "FirstERR", e.Exception.ToString()));
        }
 
        static void ReadConsole()
        {
            ulong chID = 0;
            while (true)
            {
                if (discon)
                {
                    break;
                }
                
                string input = Console.ReadLine();
                if (discon)
                {
                    if(string.IsNullOrWhiteSpace(input))
                    {
                        return;
                    }
                }
                if (input.ToLower() == "bot.stopbot")
                {
                    _client.SetGameAsync("");
                    System.Threading.Thread.Sleep(2000);
                    _client.SetStatusAsync(UserStatus.Invisible);
                    System.Threading.Thread.Sleep(2000);
                    Program.BCMDStarted = false;
                    _client.StopAsync();
                    System.Threading.Thread.Sleep(3000);//Allow the bot to shut down fully before telling Main() to scream at user to finger the keyboard to close the console.
                    Program.discon = true;
                    break;
                }
                if (input.ToLower() == "cn_term")
                {
                    Console.WriteLine("Termination");
                    break;
                }
                if (input.ToLower() == "bot.disablecmd")
                {
                    Program.LogToConsole(new LogMessage(LogSeverity.Warning, "Console", "Command processing disabled!"));
 
                    _client.SetStatusAsync(UserStatus.DoNotDisturb);
                    _client.SetGameAsync("");
                    MessagesDisabled = true;
                }
                if (input.ToLower() == "bot.enablecmd")
                {
                    Program.LogToConsole(new LogMessage(LogSeverity.Info, "Console", "Command processing enabled."));
 
                    _client.SetStatusAsync(UserStatus.Online);
                    _client.SetGameAsync("READY!");
                    MessagesDisabled = false;
                }
                if (input.ToLower().StartsWith("bot.status"))
                {
                    string status = input.Remove(0, 10).Trim();
                    Program.LogToConsole(new LogMessage(LogSeverity.Warning, "Client", "client status changed."));
 
                    _client.SetGameAsync(status);
                }
                if (input.StartsWith("setgch"))
                {
                    input = input.Remove(0, 6).Trim();
                    if (!ulong.TryParse(input, out chID))
                    {
                        Program.LogToConsole(new LogMessage(LogSeverity.Error, "Console", "Invalid ULONG."));
 
                        continue;
                    }
                }
                if (input.StartsWith("conmsg"))
                {
                    input = input.Remove(0, 6).Trim();
                    SocketTextChannel Channel = _client.GetChannel(chID) as SocketTextChannel;
                    if (Channel == null)
                    {
                        Program.LogToConsole(new LogMessage(LogSeverity.Warning, "Console", "Invalid channel."));
 
                        continue;
                    }
                    Channel.SendMessageAsync(input);
                }
               
            }
        }
 
        private static void SetupWizard()
        {
            if (InitializeConfig())//If true, do setup.
            {
                #region Page 1
                ConsoleGUIReset(ConsoleColor.Cyan, ConsoleColor.Black, "Welcome");
                Console.WriteLine("Welcome to the initial setup wizard for your new modular discord bot...\r\n Some things to note before we get started:");
                Console.WriteLine("\t- This bot requires a token. If you don't know what that is," +
                    " please visit this site first!");
                Console.WriteLine("\t- https://discordapp.com/developers/docs/intro");
 
                Console.WriteLine("\t- Privacy notice: This application will output any message that mentions the bot, or messages that start with ! to the console.\r\n\t" +
                    " THEY DO NOT GET SAVED OR POSTED ANYWHERE ELSE... That would be creepy and probably illegal...");
 
                Console.WriteLine("\r\nPress ENTER to continue...");
                Console.ReadLine();
                #endregion
 
                #region Page 2
                ConsoleGUIReset(ConsoleColor.Green, ConsoleColor.Black, "Step 1: Token Configuration");
 
                Console.WriteLine("As mentioned before, this bot requires a token. It can be added in one of two ways.");
                Console.WriteLine("\t- METHOD 1: Program arguments. Simply launch the program from a batch file,\r\n\t- USAGE: RMSoftwareModularBot.exe <put token here>");
                Console.WriteLine("\t- METHOD 2: Configure it. Actually, let's do that now.");
                Console.WriteLine("Go ahead and find your token and copy/paste* it here, then press enter.");
                Console.WriteLine("*Only if your CMD console will let you... If not, that sucks... You can painfully type it in also...");
                Console.Write("> ");
                string conf_Token = Console.ReadLine();
                
                if (!WizardDebug)
                {
                    MainCFG.CreateCategory("Application");
                    MainCFG.CreateEntry("Application", "botToken", conf_Token);
                }
 
                Console.WriteLine("Okay! Please remember, You can also start the program with a token as an argument.");
                Console.WriteLine("Be advised, the token in the configuration is ignored when using the program parameter.");
 
                Console.WriteLine("\r\nPress ENTER to continue...");
                Console.ReadLine();
                #endregion
 
                #region Page 3
                ConsoleGUIReset(ConsoleColor.Green, ConsoleColor.Black, "Step 2: Control Channel & Command prefix");
 
                Console.WriteLine("Ah, yes... The Control channel...");
                Console.WriteLine("\t- Whenever you start your bot, it will execute AutoStart.bcmd, a script full of commands to prepare for that bot life...");
                Console.WriteLine("\t- However, in order to do this, the bot needs to know where to send these messages and commands...");
                Console.WriteLine("\t- Not doing this now would result in a crashing bot that doesn't know what to do with its life.");
                Console.WriteLine("\t- All I need is the ID of that channel to put into the configuration.");
                Console.WriteLine("\t- If you need a refresher on how to find a channel ID: https://goo.gl/nqAbhw \r\n\t" +
                    "(It goes to the discord's official docs)");
                Console.WriteLine("copy/paste or painfully type in your Initialization Channel's id and press enter");
                Console.Write("> ");
                string conf_BotChannel = Console.ReadLine();
                if (!WizardDebug)
                {
                    Program.MainCFG.CreateEntry("Application", "botChannel", conf_BotChannel);
                }
                Console.WriteLine("Great! Now that channel will be the bot's main log channel...");
                Console.WriteLine();
                Console.WriteLine("Now... The Command Prefix: Please enter a single character (Recommended: A symbol of some kind), to use as the bot's command prefix");
                Console.Write("> ");
                int conf_cmdPrefix = Console.Read();
                if (!WizardDebug)
                {
                    Program.MainCFG.CreateEntry("Application", "cmdPrefix", conf_cmdPrefix);
                    Program.MainCFG.SaveConfiguration();//save
                }
                Console.WriteLine("Great! Your bot will use '" + Convert.ToChar(conf_cmdPrefix) + "' [without quotes] as a prefix for it's commands!");
                Console.WriteLine("\r\nPress ENTER to continue...");
                Console.ReadLine();
 
                #endregion
 
                #region Page 4
                ConsoleGUIReset(ConsoleColor.Cyan, ConsoleColor.Black, "Step 3: Final Notes & ProTips");
                Console.WriteLine("That is all the configuration for right now! Here are a few more things to know:");
                Console.WriteLine("\t- If you want to re-run this configuration wizard, delete the 'rmsftModBot.ini' file in the program directory.");
                Console.WriteLine("\t- The source code for this bot is available on http://rmsoftware.org");
                Console.WriteLine("\r\nCORE Command usage (in discord):");
                Console.WriteLine("\t- use <prefix>addmgrole [@roles] to add roles to the command user database.");
                Console.WriteLine("\t- usage: <prefix>addcmd <command name> <CmdMgmtOnly[true/false]> <LockToGuild[true/false]> <action>");
                Console.WriteLine("\t- Actions: Any text/emotes with optional formatting.");
                Console.WriteLine("\t- <prefix>addcmd sample1SplitParam false false splitparam 3|" +
                    " This is a sample of splitparam. Var1: {0} var2: {1} and var3: {2} all walked into a bar");
                Console.WriteLine("\t- <prefix>addcmd hug false false You hug {params} for a long time");
                Console.WriteLine("\t- More Action parameters: EXEC and CLI_EXEC ");
                Console.WriteLine("\t- <prefix>addcmd exectest falase false EXEC modname.dll ModNameSpace.ModClass StaticMethod {params}");
                Console.WriteLine("\t- <prefix>addcmd exectest falase false CLI_EXEC modname.dll ModNameSpace.ModClass StaticMethod {params}");
                Console.WriteLine("\t  - NOTE: splitparam is not supported for EXEC or CLI_EXEC");
                Console.WriteLine("\t  - NOTE: EXEC: Allows you to execute a class method for a more advanced command");
                Console.WriteLine("\t  - NOTE: CLI_EXEC is the same thing, but it gives the class access to the bot directly...");
                Console.WriteLine("Extra Configuration Options:");
                Console.WriteLine("\t  - Adding the line DisableCore=True to the [Application] section of the config file, will disable all core commands.");
                Console.WriteLine("\t     - Remember this will disable the ability to manage commands. putting the bot in a sort of 'read-only' state.");
           
                Console.WriteLine("Override core: ");
                Console.WriteLine("\t  - You can create custom commands that use the same name as core commands. This is useful for overriding core commands for even more customization...");
                Console.WriteLine("\r\nPlease visit http://rmsoftware.org/rmsoftwareModularBot for more information and documentation.");
                Console.WriteLine("\r\nPress ENTER to launch the bot!");
                Console.ReadLine();
                #endregion
            }
        }
 
 
        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            //Exception ex = e.ExceptionObject as Exception;
            //LogToConsole("CritERR", ex.Message);
            //ConsoleColor Last = Console.ForegroundColor;
            //Console.ForegroundColor = ConsoleColor.Gray;
            //LogToConsole("ExStack\r\n\r\n", ex.StackTrace);
            //Console.ForegroundColor = Last;
            CriticalError = true;
            RestartRequested = true;
            crashException = (Exception)e.ExceptionObject;
            using (FileStream fs = File.Create("CRASH.LOG"))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    LogMessage m = new LogMessage(LogSeverity.Critical, "Session", crashException.Message, crashException);
                    sw.WriteLine(m.ToString());
                    List<string> restart_args = new List<string>();
                    restart_args.AddRange(ARGS);
                    if (!restart_args.Contains("-crashed"))
                    {
                        restart_args.Add("-crashed");
                    }
                    ARGS = restart_args.ToArray();
                    sw.Flush();
 
                }
            }
            using (FileStream fs = new FileStream("ERRORS.LOG", FileMode.Append))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
 
                    sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy") + "   " + ((Exception)e.ExceptionObject).ToString());
                    sw.Flush();
 
                }
            }
        }
        static Func<bool> BotShutdown = delegate ()
        {
            return discon;
        };
 
        /// <summary>
        /// Reset the console layout using specified values
        /// </summary>
        /// <param name="fore">Text color</param>
        /// <param name="back">Background color</param>
        /// <param name="title">Console's header title (not window title)</param>
        public static void ConsoleGUIReset(ConsoleColor fore, ConsoleColor back, string title)
        {
            if(LOG_ONLY_MODE)
            {
                return;
            }
            Console.Clear();
            Console.SetWindowSize(144, 32);
            Console.SetBufferSize(144, 256);
            Console.BackgroundColor = back;
            Console.ForegroundColor = fore;
            Console.Clear();
            
            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.White;
            DecorateTop();
            string WTitle = (""+DateTime.Now.ToString("HH:mm:ss") + " " + title + " - RMSoftwareModularBot v" + Assembly.GetExecutingAssembly().GetName().Version.ToString(4));
            string pTitle = WTitle.PadLeft(71+WTitle.Length/2);
            pTitle += "".PadRight(71-WTitle.Length/2);
            Console.Write("\u2551{0}\u2551", pTitle);
            DecorateBottom();
            Console.BackgroundColor = back;
            Console.ForegroundColor = fore;
        }
 
        /// <summary>
        /// Reset the console layout using specified values
        /// </summary>
        /// <param name="fore">Text color</param>
        /// <param name="back">Background color</param>
        /// <param name="title">Console's header title (not window title)</param>
        /// <param name="w">Console window & buffer width</param>
        /// <param name="h">Console window & buffer height</param>
        public static void ConsoleGUIReset(ConsoleColor fore, ConsoleColor back, string title,short w, short h)
        {
            if (LOG_ONLY_MODE)
            {
                return;
            }
            Console.Clear();
            Console.SetWindowSize(w, h);
            Console.SetBufferSize(w, h);
            Console.BackgroundColor = back;
            Console.ForegroundColor = fore;
            Console.Clear();
 
            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.White;
            DecorateTop();
            string WTitle = ("" + DateTime.Now.ToString("HH:mm:ss") + " " + title + " - RMSoftwareModularBot v" + Assembly.GetExecutingAssembly().GetName().Version.ToString(4));
            string pTitle = WTitle.PadLeft(((w/2)+2) + WTitle.Length / 2);
            pTitle += "".PadRight(((w / 2)-3) - WTitle.Length / 2);
            Console.Write("\u2551{0}\u2551", pTitle);
 
 
            DecorateBottom();
            Console.BackgroundColor = back;
            Console.ForegroundColor = fore;
        }
 
        private static void DecorateTop()
        {
            for (int i = 0; i < Console.WindowWidth; i++)
            {
                if(i==0)
                {
                    Console.Write("\u2554");
                    continue;
                }
                
                if (i == Console.WindowWidth-1)
                {
                    Console.Write("\u2557");
                    break;
                }
                Console.Write("\u2550");
            }
        }
 
        private static void DecorateBottom()
        {
            for (int i = 0; i < Console.WindowWidth; i++)
            {
                if (i == 0)
                {
                    Console.Write("\u255A");
                    continue;
                }
 
                if (i == Console.WindowWidth - 1)
                {
                    Console.Write("\u255D");
                    break;
                }
                Console.Write("\u2550");
            }
        }
 
        public static void LogToConsole(LogMessage msg)
        {
            if(!LOG_ONLY_MODE)
            {
                
                //Console.WriteLine(l.ToString());
                writer.push(msg);
            }
            else
            {
                Console.WriteLine(JsonConvert.SerializeObject(msg));
            }
            if (msg.Exception != null)
            {
                using (FileStream fs = new FileStream("ERRORS.LOG", FileMode.Append))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
 
                        sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy") + "   " + msg.ToString());
                        sw.Flush();
 
                    }
                }
            }
        }
 
 
        /// <summary>
        /// Initialize configuration.
        /// </summary>
        /// <returns>True if it generated a new file, or has zero categories in said file.</returns>
        private static bool InitializeConfig()
        {
            ConsoleGUIReset(ConsoleColor.Cyan, ConsoleColor.Black, "Initializing Configuration");
            Program.MainCFG = new INIFile("rmsftModBot.ini");
 
            if (Program.MainCFG.Categories.Count == 0)
            {
                return true;
            }
            else
            {
                if (Program.MainCFG.CheckForCategory("Application"))
                {
                    if (Program.MainCFG.GetCategoryByName("Application").CheckForEntry("Dev-ShowWizard"))
                    {
                        WizardDebug = true;
                        return Program.MainCFG.GetCategoryByName("Application").GetEntryByName("Dev-ShowWizard").GetAsBool();
                    }
 
                    //REQUIRED entries.
                    if (!Program.MainCFG.GetCategoryByName("Application").CheckForEntry("botToken") ||
                        !Program.MainCFG.GetCategoryByName("Application").CheckForEntry("botChannel") ||
                        !Program.MainCFG.GetCategoryByName("Application").CheckForEntry("cmdPrefix"))
                    {
                        Program.LogToConsole(new LogMessage(LogSeverity.Warning, "Warning", "Invalid or missing config. Reset all settings."));
 
                        File.Delete("rmsftModBot.ini");
                        Program.MainCFG = new INIFile("rmsftModBot.ini");//Re-write from scratch.
                        return true;
                    }
                }
            }
            return false;//basically the file is found and nothing broke.
 
        }
 
 
        #region Console pixel art - Courtesy of Stackoverflow.com
        static int[] cColors = { 0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0, 0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF };
        private static bool term;
 
        public static void ConsoleWritePixel(System.Drawing.Color cValue)
        {
            System.Drawing.Color[] cTable = cColors.Select(x => System.Drawing.Color.FromArgb(x)).ToArray();
            char[] rList = new char[] { (char)9617, (char)9618, (char)9619, (char)9608 }; // 1/4, 2/4, 3/4, 4/4
            int[] bestHit = new int[] { 0, 0, 4, int.MaxValue }; //ForeColor, BackColor, Symbol, Score
 
            for (int rChar = rList.Length; rChar > 0; rChar--)
            {
                for (int cFore = 0; cFore < cTable.Length; cFore++)
                {
                    for (int cBack = 0; cBack < cTable.Length; cBack++)
                    {
                        int R = (cTable[cFore].R * rChar + cTable[cBack].R * (rList.Length - rChar)) / rList.Length;
                        int G = (cTable[cFore].G * rChar + cTable[cBack].G * (rList.Length - rChar)) / rList.Length;
                        int B = (cTable[cFore].B * rChar + cTable[cBack].B * (rList.Length - rChar)) / rList.Length;
                        int iScore = (cValue.R - R) * (cValue.R - R) + (cValue.G - G) * (cValue.G - G) + (cValue.B - B) * (cValue.B - B);
                        if (!(rChar > 1 && rChar < 4 && iScore > 50000)) // rule out too weird combinations
                        {
                            if (iScore < bestHit[3])
                            {
                                bestHit[3] = iScore; //Score
                                bestHit[0] = cFore;  //ForeColor
                                bestHit[1] = cBack;  //BackColor
                                bestHit[2] = rChar;  //Symbol
                            }
                        }
                    }
                }
            }
            Console.ForegroundColor = (ConsoleColor)bestHit[0];
            Console.BackgroundColor = (ConsoleColor)bestHit[1];
            Console.Write(rList[bestHit[2] - 1]);
        }
 
        public static void ConsoleWriteImage(System.Drawing.Bitmap source)
        {
            if (LOG_ONLY_MODE)
            {
                return;
            }
            int sMax = 39;
            decimal percent = Math.Min(decimal.Divide(sMax, source.Width), decimal.Divide(sMax, source.Height));
            System.Drawing.Size dSize = new System.Drawing.Size((int)(source.Width * percent), (int)(source.Height * percent));
            System.Drawing.Bitmap bmpMax = new System.Drawing.Bitmap(source, dSize.Width * 2, dSize.Height);
            for (int i = 0; i < dSize.Height; i++)
            {
                for (int j = 0; j < dSize.Width; j++)
                {
                    ConsoleWritePixel(bmpMax.GetPixel(j * 2, i));
                    ConsoleWritePixel(bmpMax.GetPixel(j * 2 + 1, i));
                }
                System.Console.WriteLine();
            }
            Console.ResetColor();
        }
        #endregion
 
        #endregion
 
        #region Async tasks
        public async Task LoadModules()
        {
            cmdsvr = new CommandService();
            serviceCollection = serviceCollection.AddSingleton(_client);
            serviceCollection = serviceCollection.AddSingleton(cmdsvr);
 
            foreach (string item in Directory.EnumerateFiles("CMDModules","*.dll",SearchOption.TopDirectoryOnly))
            {
                LogToConsole(new LogMessage(LogSeverity.Info,"Modules", $"Adding commands from module library: {item}"));
                try
                {
                    Assembly asmb = Assembly.LoadFile(Path.GetFullPath(item));
                    string serviceFilename = $"CMDModules\\services.{Path.GetFileNameWithoutExtension(item)}.ini";
                    string servicefilePath = Path.GetFullPath(serviceFilename);
                    if(File.Exists(servicefilePath))
                    {
                        INIFile serviceFile = new INIFile(servicefilePath);
                        foreach (INIEntry entryitem in serviceFile.GetCategoryByName("Services").Entries)
                        {
                            string typename = entryitem.GetAsString();
                            LogToConsole(new LogMessage(LogSeverity.Info,"Modules", $"Injecting service: {typename} from {asmb.GetName().Name}"));
                            serviceCollection = serviceCollection.AddSingleton(asmb.GetType(typename));
                        }
                    }
                    await cmdsvr.AddModulesAsync(asmb);//ADD EXTERNAL.
                }
                catch (Exception ex)
                {
                    Program.LogToConsole(new LogMessage(LogSeverity.Error, "CritERR",ex.Message,ex));
 
                    using (FileStream fs = new FileStream("ERRORS.LOG", FileMode.Append))
                    {
                        using (StreamWriter sw = new StreamWriter(fs))
                        {
 
                            sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy") + "   " + ex.ToString());
                            sw.Flush();
 
                        }
                    }
                }
            }
            services = serviceCollection.BuildServiceProvider();
            //check cfg for disabled core.
            if(MainCFG.GetCategoryByName("Application").CheckForEntry("disableCore"))
            {
                if(MainCFG.GetCategoryByName("Application").GetEntryByName("disableCore").GetAsBool())
                {
                    LogToConsole(new LogMessage(LogSeverity.Warning,"Program", "RMSoftware.ModularBOT CORE was disabled in the configuration."));
                    LogToConsole(new LogMessage(LogSeverity.Warning,"Program", "You will not be able to manage existing commands, nor add new ones, via discord."));
                    return;//DO NOT LOAD CORE IF THIS IS TRUE.
                }
            }
            await cmdsvr.AddModulesAsync(Assembly.GetEntryAssembly());//ADD CORE.
        }
 
        public async Task MainAsync(string token)
        {
            try
            {
                ConsoleGUIReset(ConsoleColor.White, ConsoleColor.DarkBlue, "Application Running");
 
                _client = new DiscordSocketClient();
                _client.Log += Log;
                serviceCollection = new ServiceCollection();
 
                _client.Ready += _ClientReady;
                _client.Connected += _client_Connected;
                _client.MessageReceived += _client_MessageReceived;
                _client.Disconnected += _client_Disconnected;
 
                await LoadModules();//ADD CORE AND EXTERNAL MODULES
                await _client.LoginAsync(TokenType.Bot, token);
                await _client.StartAsync();
            }
            catch (Exception ex)
            {
                CriticalError = true;
                RestartRequested = true;
                crashException = ex;
                using (FileStream fs = File.Create("CRASH.LOG"))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        LogMessage m = new LogMessage(LogSeverity.Critical, "Exception", crashException.Message, crashException);
                        sw.WriteLine(m.ToString());
                        List<string> restart_args = new List<string>();
                        restart_args.AddRange(ARGS);
                        if (!restart_args.Contains("-crashed"))
                        {
                            restart_args.Add("-crashed");
                        }
                        ARGS = restart_args.ToArray();
                        sw.Flush();
                    }
                }
                using (FileStream fs = new FileStream("ERRORS.LOG", FileMode.Append))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
 
                        sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy") + "   " + ex.ToString());
                        sw.Flush();
 
                    }
                }
            }
                   
        }
 
        private Task _client_Connected()
        {
            StartTime = DateTime.Now;
            LogToConsole(new LogMessage(LogSeverity.Info,"Uptime", "Reset uptime to ALL zero."));
            Console.Title = "RMSoftware.ModularBOT -> " + _client.CurrentUser.Username + " | Connected to " + _client.Guilds.Count + " guilds.";
            return Task.Delay(1);
        }
 
        private Task _client_Disconnected(Exception arg)
        {
            LogToConsole(new LogMessage(LogSeverity.Warning,"Session", "Disconnected: "+ arg.Message,arg));
            Console.Title = "RMSoftware.ModularBot - Disconnected";
            LogToConsole(new LogMessage(LogSeverity.Info,"Uptime", "The client is disconnected."));
            
            return Task.Delay(3);
        }
 
        private async Task _ClientReady()
        {
            await Log(new LogMessage(LogSeverity.Info, "TaskMgr", "Running Onstart.bcmd - task"));
            await Task.Run(new Action(OffloadReady));
            Console.Title = "RMSoftware.ModularBOT -> " + _client.CurrentUser.Username + " | Connected to " + _client.Guilds.Count + " guilds.";
          
        }
 
        private async void OffloadReady()
        {
            try
            {
                if (!BCMDStarted)
                {
                    await _client.SetStatusAsync(UserStatus.DoNotDisturb);
                    //PROCESS THE AutoEXEC file
                    ulong id = MainCFG.GetCategoryByName("Application").GetEntryByName("botChannel").GetAsUlong();
                    using (StreamReader fs = File.OpenText("OnStart.bcmd"))
                    {
                        while (!fs.EndOfStream)
                        {
                            string line = await fs.ReadLineAsync();
                            if (!string.IsNullOrWhiteSpace(line))
                            {
                                if (!line.StartsWith(@"\\"))
                                {
                                    IGuildChannel ch = _client.GetChannel(id) as IGuildChannel;
                                    //line = line.Replace("%p_", CommandPrefix.ToString());
                                    if (CRASH)
                                    {
                                        ConsoleColor last = Console.BackgroundColor;
                                        Console.BackgroundColor = ConsoleColor.DarkRed;
                                        LogToConsole(new LogMessage(LogSeverity.Warning,"Program", "The program was auto-restarted due to a crash. Please see CRASH.LOG for details on what happened."));
                                        Console.BackgroundColor = last;
                                        await ((IMessageChannel)ch).SendMessageAsync("**WARNING:** `The bot was auto-restarted due to a crash. Please see CRASH.LOG for details on what happened.`");
                                        CRASH = false;//don't make it happen more than once pls.
                                    }
                                    if (!line.StartsWith("%p_"))
                                    {
 
                                        await ((IMessageChannel)ch).SendMessageAsync(line);
                                    }
                                    else
                                    {
 
 
                                        line = line.Replace("%p_", CommandPrefix.ToString());
                                        if (await ccmg.Process(new PsuedoMessage(line, _client.CurrentUser, ch, MessageSource.User)))
                                        {
                                            LogToConsole(new LogMessage(LogSeverity.Info,"OnStart", line));
                                            LogToConsole(new LogMessage(LogSeverity.Info, "OnStart", "CustomCMD Success..."));
                                            continue;
                                        }
                                        //Damn, I can't be sassy here... If it was a command, but not a ccmg command, then try the context for modules. If THAT didn't work
                                        //Then it will output the result of the context.
                                        var context = new CommandContext(_client, new PsuedoMessage(line,_client.CurrentUser,ch,MessageSource.User));
                                        // Execute the command. (result does not indicate a return value, 
                                        // rather an object stating if the command executed successfully)
                                        var result = await cmdsvr.ExecuteAsync(context, 1, services);
                                        LogToConsole(new LogMessage(LogSeverity.Info,"OnStart", line));
                                        LogToConsole(new LogMessage(LogSeverity.Info,"OnStart", result.ToString()));
                                    }
                                    await Task.Delay(250);
                                }
                            }
                        }
                    }
                    BCMDStarted = true;
                    await _client.SetGameAsync("READY!");
                    LogToConsole(new LogMessage(LogSeverity.Info,"TaskMgr", "Task is complete."));
                    await _client.SetStatusAsync(UserStatus.Online);
                    LogToConsole(new LogMessage(LogSeverity.Info,"TaskMgr", "Running Message Queue - task"));
                    foreach (var item in messageQueue)
                    {
                        
                        await _client_MessageReceived(item);
                        await Task.Delay(500);
                    }
                    LogToConsole(new LogMessage(LogSeverity.Info,"TaskMgr", "Task is complete"));
                }
            }
            catch (Exception ex)
            {
                BCMDStarted = false;
                LogToConsole(new LogMessage(LogSeverity.Error,"CritERR", ex.Message,ex));
 
            }
        }
 
        private async Task _client_MessageReceived(SocketMessage arg)
        {
            if(MessagesDisabled)
            {
                return;
            }
            if(rolemgt.UserBlacklisted(arg.Author))
            {
                return;
            }
            #region Console Output debug
            //DEBUG: Output the bot-mentioned chat message to the console
            foreach (SocketUser item in arg.MentionedUsers)
            {
                if (item.Mention == _client.CurrentUser.Mention)
                {
                    LogToConsole(new LogMessage(LogSeverity.Info,"Mention", "<[" + arg.Channel.Name + "] " + arg.Author.Username + " >: " + arg.Content));
                }
            }
            //DEBUG: output ! prefixed messages to console.
            if (arg.Content.StartsWith(CommandPrefix.ToString()))
            {
                LogToConsole(new LogMessage(LogSeverity.Info,"Command", "<[" + arg.Channel.Name + "] " + arg.Author.Username + " >: " + arg.Content));
 
            }
 
            #endregion
 
            // Don't process the command if it was a System Message
            var message = arg as SocketUserMessage;
            if (message == null) return;
            // Create a number to track where the prefix ends and the command begins
            int argPos = 0;
            // Determine if the message is a command, based on if it starts with 'commandprefix' or a mention prefix. if not, ignore it.
            if (!(message.HasCharPrefix(CommandPrefix, ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return;
            if (!arg.Author.IsBot && !BCMDStarted)
            {
                messageQueue.Add(arg);  //queue it up. The bcmdStarted check should 
                                        //provide PLENTY (if not an excessive amount) of time for modules,and extra commands 
                                        //to be fully loaded by the time it is set to true. Preemptively solving the "hey, you just ignored my commands completely" 
                                        //when the bot starts and doesn't respond to a command at first
                return;
            }
            // Create a Command Context for command modules
            //Process CoreCustom commands
            if (await ccmg.Process(arg))
            {
                return;//If the message contained a custom command from config, No need to continue.
            };
            var context = new CommandContext(_client, message);
            // Execute the command. (result does not indicate a return value, 
            // rather an object stating if the command executed successfully)
            var result = await cmdsvr.ExecuteAsync(context, argPos, services);
            //If the result is unsuccessful AND not unknown command, send the error details.
            if (result.Error.HasValue)
            {
                if (!result.Error.Value.HasFlag(CommandError.UnknownCommand) && !result.IsSuccess)
                {
                    await arg.Channel.SendMessageAsync(result.ErrorReason);
                }
                if (result.Error.Value.HasFlag(CommandError.BadArgCount))
                {
                    await arg.Channel.SendMessageAsync(result.ErrorReason);
                }
            }
 
 
        }
 
        private Task Log(LogMessage msg)
        {
            if(!LOG_ONLY_MODE)
            {
                LogToConsole(msg);
            }
            else
            {
                Console.WriteLine(JsonConvert.SerializeObject(msg));
            }
            if (msg.Message == "Failed to resume previous session")
            {
                _client.StopAsync();
                _client = null;
                crashException = new Exception("Failed to resume previous session. See ERRORS.LOG for any additional information.");
                CriticalError = true;
                discon = true;
                Thread.Sleep(3000);
            }
            if(msg.Exception != null)
            {
                using (FileStream fs = new FileStream("ERRORS.LOG",FileMode.Append))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        
                        sw.WriteLine(DateTime.Today.ToString("MM/dd/yyyy")+"   "+msg.ToString());
                        sw.Flush();
 
                    }
                }
            }
            return Task.Delay(0);
        }
 
 
        #endregion
    }
    
    #region Retry clause - API error workaround [possibly obsolete] [Stackoverflow.com]
    public static class Retry
    {
        public static void Do(
            Action action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            Do<object>(() =>
            {
                 action();
                return null;
            }, retryInterval, maxAttemptCount);
        }
 
        public static T Do<T>(
            Func<T> action,
            TimeSpan retryInterval,
            int maxAttemptCount = 3)
        {
            var exceptions = new List<Exception>();
 
            for (int attempted = 0; attempted < maxAttemptCount; attempted++)
            {
                try
                {
                    if (attempted > 0)
                    {
                        Thread.Sleep(retryInterval);
                    }
                    return action();
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                    Program.LogToConsole(new LogMessage(LogSeverity.Warning,"API_RETRY", ex.Message,ex));
                    Program.LogToConsole(new LogMessage(LogSeverity.Warning,"API_RETRY", "Retrying " + (maxAttemptCount-attempted) + " more time(s)"));
                }
            }
            throw new AggregateException(exceptions);
        }
    }
 
    #endregion
 
    #region Advanced Command Processing
    public class CmdRoleManager
    {
        INIFile mgmt;
        INIFile userBlackList;
        public CmdRoleManager()
        {
            mgmt = new INIFile("cmdMgr.ini");
            userBlackList = new INIFile("blacklist.ini");
            if(!userBlackList.CheckForCategory("Blacklist"))
            {
                userBlackList.CreateCategory("Blacklist");
                userBlackList.SaveConfiguration();
            }
        }
 
        public bool AddUserToBlacklist(SocketUser user)
        {
            string guildCat = "Blacklist";
            bool check = userBlackList.GetCategoryByName(guildCat).CheckForEntry(user.Id.ToString());
            if(!check)
            {
                userBlackList.CreateEntry(guildCat, user.Id.ToString(), user.Username.Replace("=", "_"));
            }
            userBlackList.SaveConfiguration();
            return !check;
        }
 
        public bool DeleteUserFromBlacklist(SocketUser user)
        {
            string guildCat = "Blacklist";
            bool check = userBlackList.GetCategoryByName(guildCat).CheckForEntry(user.Id.ToString());
            if (check)
            {
                userBlackList.DeleteEntry(guildCat, user.Id.ToString());
            }
            userBlackList.SaveConfiguration();
            return check;
        }
 
        public void AddCommandManagerRole(SocketRole role)
        {
            string guildCat = role.Guild.Id.ToString();
            bool check = mgmt.CheckForCategory(guildCat);
            int indx = -1;
            if(check)
            {
                indx = mgmt.GetCategoryByName(guildCat).Entries.Count-1;
                mgmt.CreateEntry(guildCat, "role" + (indx + 1), role.Id);
            }
            else
            {
                mgmt.CreateCategory(guildCat);
                indx = mgmt.GetCategoryByName(guildCat).Entries.Count - 1;
                mgmt.CreateEntry(guildCat, "role" + (indx + 1), role.Id);
            }
            mgmt.SaveConfiguration();
        }
 
        public bool UserBlacklisted(SocketUser user)
        {
            return userBlackList.GetCategoryByName("Blacklist").CheckForEntry(user.Id.ToString());
        }
 
        public string DeleteCommandManager(SocketRole role)
        {
            string guildCat = role.Guild.Id.ToString();
            bool check = mgmt.CheckForCategory(guildCat);
            int indx = -1;
            if (check)
            {
                indx = mgmt.GetCategoryByName(guildCat).Entries.Count - 1;
                try
                {
                    mgmt.DeleteEntry(guildCat, mgmt.GetCategoryByName(guildCat).Entries.Find(x => x.GetAsUlong() == role.Id).Name);
                    //Then get all entries and rename them.
 
                    for (int i = 0; i < mgmt.GetCategoryByName(guildCat).Entries.Count; i++)
                    {
                        mgmt.GetCategoryByName(guildCat).Entries[i].Name = $"role{i}";
                    }
                }
                catch (Exception ex)
                {
 
                    return ex.Message;
                }
                
            }
            else
            {
                return "No results. The guild ID didn't exist in the database.";
            }
            mgmt.SaveConfiguration();
            return "Command Manager database updated.";
        }
 
        public bool CheckUserRole(SocketGuildUser user)
        {
            string guildcat = user.Guild.Id.ToString();//if the category does not exist, return false... can't have that;
            if(!mgmt.CheckForCategory(guildcat))
            {
                return false;
            }
            foreach (var role in user.Roles)
            {
                ulong id = role.Id;
                if (mgmt.GetCategoryByName(guildcat).Entries.Exists(x => x.GetAsUlong() == id))
                {
                    return true;//keep doing it until it returns true.
                }
            }
            return false;//default;
        }
 
        public SocketRole[] GetRolesForGuild(SocketGuild guild)
        {
            List<SocketRole> items = new List<SocketRole>();
            string guildcat = guild.Id.ToString();
            if (!mgmt.CheckForCategory(guildcat))
            {
                return null;
            }
            foreach (var item in mgmt.GetCategoryByName(guildcat).Entries)
            {
                items.Add(guild.GetRole(item.GetAsUlong()));
            }
            return items.ToArray();
        }
 
        public SocketRole[] GetAllRoles()
        {
            List<SocketRole> items = new List<SocketRole>();
            foreach (var itemc in mgmt.Categories)
            {
                foreach (var item in itemc.Entries)
                {
                    items.Add(Program._client.GetGuild(Convert.ToUInt64(itemc.Name)).GetRole(item.GetAsUlong()));
                }
            }
            return items.ToArray();
        }
    }
    public class PsuedoMessage : IMessage, IUserMessage
    {
        string _content = "";
        SocketUser _author;
        IGuildChannel _c;
        MessageSource _source;
        MessageType _type;
        public PsuedoMessage(string content,SocketUser author,IGuildChannel ch ,MessageSource source)
        {
            _content = content;
            _author = author;
            _c = ch;
            _source = source;
            _type = MessageType.Default;
        }
 
 
        IReadOnlyCollection<IAttachment> IMessage.Attachments
        {
            get;
        }
 
        IUser IMessage.Author
        {
            get { return _author; }
        }
 
        IMessageChannel IMessage.Channel
        {
            get { return _c as IMessageChannel; }
        }
 
        string IMessage.Content
        {
            get { return _content; }
        }
 
        DateTimeOffset ISnowflakeEntity.CreatedAt
        {
            get;
        }
 
        DateTimeOffset? IMessage.EditedTimestamp
        {
            get;
        }
 
        IReadOnlyCollection<IEmbed> IMessage.Embeds
        {
            get;
        }
 
        ulong IEntity<ulong>.Id
        {
            get { return (ulong)new Random().Next(0,int.MaxValue); }
        }
 
        bool IMessage.IsPinned
        {
            get { return false; }
        }
 
        bool IMessage.IsTTS
        {
            get { return false; }
        }
 
        IReadOnlyCollection<ulong> IMessage.MentionedChannelIds
        {
            get;
        }
 
        IReadOnlyCollection<ulong> IMessage.MentionedRoleIds
        {
            get;
        }
 
        IReadOnlyCollection<ulong> IMessage.MentionedUserIds
        {
            get;
        }
 
        MessageSource IMessage.Source
        {
            get { return _source; }
        }
 
        IReadOnlyCollection<ITag> IMessage.Tags
        {
            get;
        }
 
        DateTimeOffset IMessage.Timestamp
        {
            get;
        }
 
        MessageType IMessage.Type
        {
            get { return _type; }
        }
 
        public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions
        {
            get
            {
                throw new NotImplementedException();
            }
        }
 
        Task IDeletable.DeleteAsync(RequestOptions options)
        {
            return Task.Delay(0);
        }
 
        public Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task PinAsync(RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task UnpinAsync(RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task AddReactionAsync(IEmote emote, RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task RemoveAllReactionsAsync(RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public Task<IReadOnlyCollection<IUser>> GetReactionUsersAsync(string emoji, int limit = 100, ulong? afterUserId = default(ulong?), RequestOptions options = null)
        {
            throw new NotImplementedException();
        }
 
        public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name)
        {
            throw new NotImplementedException();
        }
    }
 
    #endregion
}

Comments