diff --git a/.gitignore b/.gitignore index a12e00a..1deecb5 100644 --- a/.gitignore +++ b/.gitignore @@ -373,4 +373,6 @@ FodyWeavers.xsd /DiscordBot/Updater/ .idea/ DiscordBot/Launcher.exe -DiscordBotUI/* +DiscordBotUI/bin +DiscordBotUI/obj +/.vscode diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e141305..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/DiscordBot/bin/Debug/net6.0/DiscordBot.dll", - "args": [], - "cwd": "${workspaceFolder}/DiscordBot/bin/Debug/net6.0/", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "externalTerminal", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 08f8fa7..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/DiscordBot/DiscordBot.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/DiscordBot/DiscordBot.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/DiscordBot/DiscordBot.csproj" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/DiscordBot/Bot/Actions/Clear.cs b/DiscordBot/Bot/Actions/Clear.cs index a4bf0e3..3be31da 100644 --- a/DiscordBot/Bot/Actions/Clear.cs +++ b/DiscordBot/Bot/Actions/Clear.cs @@ -1,16 +1,21 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using PluginManager.Interfaces; using PluginManager.Others; +using PluginManager.Others.Actions; namespace DiscordBot.Bot.Actions; -public class Clear : ICommandAction +public class Clear: ICommandAction { - public string ActionName => "clear"; - public string Description => "Clears the console"; - public string Usage => "clear"; - public InternalActionRunType RunType => InternalActionRunType.ON_CALL; + public string ActionName => "clear"; + public string Description => "Clears the console"; + public string Usage => "clear"; + public IEnumerable ListOfOptions => []; + + public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public Task Execute(string[] args) { diff --git a/DiscordBot/Bot/Actions/Exit.cs b/DiscordBot/Bot/Actions/Exit.cs index 1caa641..612ec7f 100644 --- a/DiscordBot/Bot/Actions/Exit.cs +++ b/DiscordBot/Bot/Actions/Exit.cs @@ -1,29 +1,36 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using PluginManager; using PluginManager.Interfaces; using PluginManager.Others; +using PluginManager.Others.Actions; namespace DiscordBot.Bot.Actions; -public class Exit : ICommandAction +public class Exit: ICommandAction { - public string ActionName => "exit"; - public string Description => "Exits the bot and saves the config. Use exit help for more info."; - public string Usage => "exit [help|force (-f)]"; - public InternalActionRunType RunType => InternalActionRunType.ON_CALL; + public string ActionName => "exit"; + public string Description => "Exits the bot and saves the config. Use exit help for more info."; + public string Usage => "exit "; + public IEnumerable ListOfOptions => new List + { + new InternalActionOption("help", "Displays this message"), + new InternalActionOption("force | -f", "Exits the bot without saving the config") + }; + public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public async Task Execute(string[] args) { if (args is null || args.Length == 0) { - Config.Logger.Log("Exiting...", source: typeof(ICommandAction), type: LogType.WARNING); + Config.Logger.Log("Exiting...", typeof(ICommandAction), LogType.WARNING); await Config.AppSettings.SaveToFile(); Environment.Exit(0); } else { - switch ( args[0] ) + switch (args[0]) { case "help": Console.WriteLine("Usage : exit [help|force]"); @@ -33,7 +40,7 @@ public class Exit : ICommandAction case "-f": case "force": - Config.Logger.Log("Exiting (FORCE)...", source: typeof(ICommandAction), type: LogType.WARNING); + Config.Logger.Log("Exiting (FORCE)...", typeof(ICommandAction), LogType.WARNING); Environment.Exit(0); break; diff --git a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs b/DiscordBot/Bot/Actions/Extra/PluginMethods.cs index 3b96d7d..1e71bf2 100644 --- a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs +++ b/DiscordBot/Bot/Actions/Extra/PluginMethods.cs @@ -1,204 +1,206 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; +using System.Threading; using System.Threading.Tasks; + using DiscordBot.Utilities; + using PluginManager; using PluginManager.Interfaces; using PluginManager.Loaders; using PluginManager.Online; using PluginManager.Others; + using Spectre.Console; namespace DiscordBot.Bot.Actions.Extra; internal static class PluginMethods { - private static readonly PluginsManager PluginsManager = new(); - - internal static async Task List() + internal static async Task List(PluginsManager manager) { - var data = await ConsoleUtilities.ExecuteWithProgressBar(PluginsManager.GetAvailablePlugins(), "Loading plugins..."); - - TableData tableData = new(new List { "Name", "Description", "Type", "Version" }); - foreach (var plugin in data) tableData.AddRow(plugin); - + var data = await ConsoleUtilities.ExecuteWithProgressBar(manager.GetPluginsList(), "Reading remote database"); + + TableData tableData = new(["Name", "Description", "Version", "Is Installed"]); + + var installedPlugins = await ConsoleUtilities.ExecuteWithProgressBar(manager.GetInstalledPlugins(), "Reading local database "); + + foreach (var plugin in data) + { + bool isInstalled = installedPlugins.Any(p => p.PluginName == plugin.Name); + tableData.AddRow([plugin.Name, plugin.Description, plugin.Version.ToString(), isInstalled ? "Yes" : "No"]); + } + tableData.HasRoundBorders = false; - tableData.PrintAsTable(); + tableData.PrintTable(); } - + internal static async Task RefreshPlugins(bool quiet) { await Program.internalActionManager.Execute("plugin", "load", quiet ? "-q" : string.Empty); await Program.internalActionManager.Refresh(); } - + internal static async Task DownloadPlugin(PluginsManager manager, string pluginName) { - var pluginData = await manager.GetPluginLinkByName(pluginName); - if (pluginData.Length == 0) + var pluginData = await manager.GetPluginDataByName(pluginName); + if (pluginData is null) { Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again."); return; } - var pluginType = pluginData[0]; - var pluginLink = pluginData[1]; - var pluginRequirements = pluginData[2]; - - + var pluginLink = pluginData.DownLoadLink; + + await AnsiConsole.Progress() - .Columns(new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn() - }) - .StartAsync(async ctx => - { - var downloadTask = ctx.AddTask("Downloading plugin..."); + .Columns(new ProgressColumn[] + { + new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn() + } + ) + .StartAsync(async ctx => + { + var downloadTask = ctx.AddTask("Downloading plugin..."); - IProgress progress = new Progress(p => { downloadTask.Value = p; }); + IProgress progress = new Progress(p => { downloadTask.Value = p; }); - await ServerCom.DownloadFileAsync(pluginLink, $"./Data/{pluginType}s/{pluginName}.dll", progress); - - downloadTask.Increment(100); - - ctx.Refresh(); - }); + await ServerCom.DownloadFileAsync(pluginLink, $"{Config.AppSettings["PluginFolder"]}/{pluginName}.dll", progress); - if (pluginRequirements == string.Empty) + downloadTask.Increment(100); + + ctx.Refresh(); + } + ); + + if (!pluginData.HasDependencies) { + await manager.AppendPluginToDatabase(new PluginManager.Plugin.PluginInfo(pluginName, pluginData.Version, [])); Console.WriteLine("Finished installing " + pluginName + " successfully"); await RefreshPlugins(false); return; } - List requirementsUrLs = new(); - + List, string, string>> downloadTasks = new(); await AnsiConsole.Progress() - .Columns(new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn() - }) - .StartAsync(async ctx => - { - var gatherInformationTask = ctx.AddTask("Gathering info..."); - gatherInformationTask.IsIndeterminate = true; - requirementsUrLs = await ServerCom.ReadTextFromURL(pluginRequirements); - await Task.Delay(2000); - gatherInformationTask.Increment(100); - }); + .Columns(new ProgressColumn[] + { + new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn() + } + ) + .StartAsync(async ctx => + { - await AnsiConsole.Progress() - .Columns(new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn() - }) - .StartAsync(async ctx => - { - List, Task>> downloadTasks = new(); - foreach (var info in requirementsUrLs) - { - if (info.Length < 2) continue; - string[] data = info.Split(','); - string url = data[0]; - string fileName = data[1]; - - var task = ctx.AddTask($"Downloading {fileName}..."); - IProgress progress = new Progress(p => - { - task.Value = p; - }); - - var downloadTask = ServerCom.DownloadFileAsync(url, $"./{fileName}", progress); - downloadTasks.Add(new Tuple, Task>(task, progress, downloadTask)); - } - - foreach (var task in downloadTasks) - { - await task.Item3; - } - - }); - + foreach (var dependency in pluginData.Dependencies) + { + var task = ctx.AddTask($"Downloading {dependency.DownloadLocation}: "); + IProgress progress = new Progress(p => + { + task.Value = p; + } + ); + + task.IsIndeterminate = true; + downloadTasks.Add(new Tuple, string, string>(task, progress, dependency.DownloadLink, dependency.DownloadLocation)); + } + + int maxParallelDownloads = 5; + + if (Config.AppSettings.ContainsKey("MaxParallelDownloads")) + maxParallelDownloads = int.Parse(Config.AppSettings["MaxParallelDownloads"]); + + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = maxParallelDownloads, + TaskScheduler = TaskScheduler.Default + }; + + await Parallel.ForEachAsync(downloadTasks, options, async (tuple, token) => + { + tuple.Item1.IsIndeterminate = false; + await ServerCom.DownloadFileAsync(tuple.Item3, $"./{tuple.Item4}", tuple.Item2); + } + ); + + + + } + ); + + await manager.AppendPluginToDatabase(new PluginManager.Plugin.PluginInfo(pluginName, pluginData.Version, pluginData.Dependencies.Select(sep => sep.DownloadLocation).ToList())); await RefreshPlugins(false); } internal static async Task LoadPlugins(string[] args) { - var loader = new PluginLoader(Config.DiscordBot.client); - if (args.Length == 2 && args[1] == "-q") - { - loader.LoadPlugins(); - return true; - } + var loader = new PluginLoader(Config.DiscordBot.Client); + if (args.Length == 2 && args[1] == "-q") + { + await loader.LoadPlugins(); + return true; + } - var cc = Console.ForegroundColor; - loader.onCMDLoad += (name, typeName, success, exception) => - { - if (name == null || name.Length < 2) - name = typeName; - if (success) - { - Config.Logger.Log("Successfully loaded command : " + name, source: typeof(ICommandAction), - type: LogType.INFO); - } + var cc = Console.ForegroundColor; + loader.OnCommandLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded command : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } - else - { - Config.Logger.Log("Failed to load command : " + name + " because " + exception?.Message, - source: typeof(ICommandAction), type: LogType.ERROR); - } + else + { + Config.Logger.Log("Failed to load command : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } - Console.ForegroundColor = cc; - }; - loader.onEVELoad += (name, typeName, success, exception) => - { - if (name == null || name.Length < 2) - name = typeName; + Console.ForegroundColor = cc; + }; + loader.OnEventLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded event : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } + else + { + Config.Logger.Log("Failed to load event : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } - if (success) - { - Config.Logger.Log("Successfully loaded event : " + name, source: typeof(ICommandAction), - type: LogType.INFO); - } - else - { - Config.Logger.Log("Failed to load event : " + name + " because " + exception?.Message, - source: typeof(ICommandAction), type: LogType.ERROR); - } + Console.ForegroundColor = cc; + }; - Console.ForegroundColor = cc; - }; + loader.OnSlashCommandLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded slash command : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } + else + { + Config.Logger.Log("Failed to load slash command : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } - loader.onSLSHLoad += (name, typeName, success, exception) => - { - if (name == null || name.Length < 2) - name = typeName; + Console.ForegroundColor = cc; + }; - if (success) - { - Config.Logger.Log("Successfully loaded slash command : " + name, source: typeof(ICommandAction), - type: LogType.INFO); - } - else - { - Config.Logger.Log("Failed to load slash command : " + name + " because " + exception?.Message, - source: typeof(ICommandAction), type: LogType.ERROR); - } - - Console.ForegroundColor = cc; - }; - - loader.LoadPlugins(); - Console.ForegroundColor = cc; - return true; + await loader.LoadPlugins(); + Console.ForegroundColor = cc; + return true; } - - + + } diff --git a/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs b/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs index b038e39..9d78f7b 100644 --- a/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs +++ b/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs @@ -1,30 +1,32 @@ using System.Linq; using PluginManager; +using PluginManager.Loaders; namespace DiscordBot.Bot.Actions.Extra; internal static class SettingsConfigExtra { - internal static void SetSettings(string key, params string[] value) + + internal static void SetSettings(string key, params string[] value) { if (key is null) return; if (value is null) return; - + if (!Config.AppSettings.ContainsKey(key)) return; - + Config.AppSettings[key] = string.Join(' ', value); - // Config.AppSettings.SaveToFile().Wait(); + // Config.AppSettings.SaveToFile().Wait(); } internal static void RemoveSettings(string key) { if (key is null) return; - - if(!Config.AppSettings.ContainsKey(key)) + + if (!Config.AppSettings.ContainsKey(key)) return; - + Config.AppSettings.Remove(key); } @@ -33,11 +35,11 @@ internal static class SettingsConfigExtra if (key is null) return; if (value is null) return; - + if (Config.AppSettings.ContainsKey(key)) return; - + Config.AppSettings.Add(key, string.Join(' ', value)); // Config.AppSettings.SaveToFile().Wait(); } -} \ No newline at end of file +} diff --git a/DiscordBot/Bot/Actions/Help.cs b/DiscordBot/Bot/Actions/Help.cs index f6e2ea5..1378b62 100644 --- a/DiscordBot/Bot/Actions/Help.cs +++ b/DiscordBot/Bot/Actions/Help.cs @@ -1,41 +1,70 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using DiscordBot.Utilities; using PluginManager.Interfaces; using PluginManager.Others; +using PluginManager.Others.Actions; +using Spectre.Console; namespace DiscordBot.Bot.Actions; -public class Help : ICommandAction +public class Help: ICommandAction { public string ActionName => "help"; public string Description => "Shows the list of commands and their usage"; - public string Usage => "help [command]"; + public string Usage => "help "; + + public IEnumerable ListOfOptions => []; public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public async Task Execute(string[] args) { + TableData tableData = new TableData(); if (args == null || args.Length == 0) { - var items = new List - { - new[] { "-", "-", "-" }, - new[] { "Command", "Usage", "Description" }, - new[] { "-", "-", "-" } - }; + + tableData.Columns = ["Command", "Usage", "Description", "Options"]; foreach (var a in Program.internalActionManager.Actions) - items.Add(new[] { a.Key, a.Value.Usage, a.Value.Description }); + { + Markup actionName = new Markup($"[bold]{a.Key}[/]"); + Markup usage = new Markup($"[italic]{a.Value.Usage}[/]"); + Markup description = new Markup($"[dim]{a.Value.Description}[/]"); + + if (a.Value.ListOfOptions.Any()) + { + + var optionsTable = new Table(); + optionsTable.AddColumn("Option"); + optionsTable.AddColumn("Description"); + + foreach (var option in a.Value.ListOfOptions) + { + + optionsTable.AddRow(option.OptionName, option.OptionDescription); + } + + tableData.AddRow([actionName, usage, description, optionsTable]); + } + else + { + tableData.AddRow([actionName, usage, description]); + } + + + } + + // render the table + tableData.HasRoundBorders = true; + tableData.DisplayLinesBetweenRows = true; + tableData.PrintTable(); - items.Add(new[] { "-", "-", "-" }); - ConsoleUtilities.FormatAndAlignTable(items, - TableFormat.CENTER_EACH_COLUMN_BASED - ); return; } @@ -46,17 +75,10 @@ public class Help : ICommandAction } var action = Program.internalActionManager.Actions[args[0]]; - var actionData = new List - { - new[] { "-", "-", "-" }, - new[] { "Command", "Usage", "Description" }, - new[] { "-", "-", "-" }, - new[] { action.ActionName, action.Usage, action.Description }, - new[] { "-", "-", "-" } - }; + tableData.Columns = ["Command", "Usage", "Description"]; + tableData.AddRow([action.ActionName, action.Usage, action.Description]); - ConsoleUtilities.FormatAndAlignTable(actionData, - TableFormat.CENTER_EACH_COLUMN_BASED - ); + + tableData.PrintTable(); } } diff --git a/DiscordBot/Bot/Actions/Plugin.cs b/DiscordBot/Bot/Actions/Plugin.cs index a24ef12..be753cf 100644 --- a/DiscordBot/Bot/Actions/Plugin.cs +++ b/DiscordBot/Bot/Actions/Plugin.cs @@ -1,23 +1,31 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using System.Windows.Input; using DiscordBot.Bot.Actions.Extra; using PluginManager; using PluginManager.Interfaces; -using PluginManager.Loaders; -using PluginManager.Online; using PluginManager.Others; -using Spectre.Console; +using PluginManager.Others.Actions; namespace DiscordBot.Bot.Actions; -public class Plugin : ICommandAction +public class Plugin: ICommandAction { private bool pluginsLoaded; public string ActionName => "plugin"; public string Description => "Manages plugins. Use plugin help for more info."; - public string Usage => "plugin [help|list|load|install|refresh]"; + public string Usage => "plugin "; + + public IEnumerable ListOfOptions => new List + { + new InternalActionOption("help", "Displays this message"), + new InternalActionOption("list", "Lists all plugins"), + new InternalActionOption("load", "Loads all plugins"), + new InternalActionOption("install", "Installs a plugin"), + new InternalActionOption("refresh", "Refreshes the plugin list"), + new InternalActionOption("uninstall", "Uninstalls a plugin") + }; + public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public async Task Execute(string[] args) @@ -34,27 +42,32 @@ public class Plugin : ICommandAction return; } - var manager = new PluginsManager(); - switch (args[0]) { case "refresh": await PluginMethods.RefreshPlugins(true); break; - + + case "uninstall": + string plugName = string.Join(' ', args, 1, args.Length-1); + bool result = await Config.PluginsManager.MarkPluginToUninstall(plugName); + if(result) + Console.WriteLine($"Marked to uninstall plugin {plugName}. Please restart the bot"); + break; + case "list": - await PluginMethods.List(); + await PluginMethods.List(Config.PluginsManager); break; case "load": if (pluginsLoaded) { - Config.Logger.Log("Plugins already loaded", source: typeof(ICommandAction), type: LogType.WARNING); + Config.Logger.Log("Plugins already loaded", typeof(ICommandAction), LogType.WARNING); break; } - + if (Config.DiscordBot is null) { - Config.Logger.Log("DiscordBot is null", source: typeof(ICommandAction), type: LogType.WARNING); + Config.Logger.Log("DiscordBot is null", typeof(ICommandAction), LogType.WARNING); break; } @@ -75,8 +88,8 @@ public class Plugin : ICommandAction } } - await PluginMethods.DownloadPlugin(manager, pluginName); + await PluginMethods.DownloadPlugin(Config.PluginsManager, pluginName); break; } } -} \ No newline at end of file +} diff --git a/DiscordBot/Bot/Actions/SettingsConfig.cs b/DiscordBot/Bot/Actions/SettingsConfig.cs index 332ed75..54389de 100644 --- a/DiscordBot/Bot/Actions/SettingsConfig.cs +++ b/DiscordBot/Bot/Actions/SettingsConfig.cs @@ -1,17 +1,26 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using DiscordBot.Bot.Actions.Extra; using PluginManager; using PluginManager.Interfaces; using PluginManager.Others; +using PluginManager.Others.Actions; namespace DiscordBot.Bot.Actions; -public class SettingsConfig : ICommandAction +public class SettingsConfig: ICommandAction { public string ActionName => "config"; public string Description => "Change the settings of the bot"; - public string Usage => "config [options] "; + public string Usage => "config "; + public IEnumerable ListOfOptions => new List + { + new InternalActionOption("help", "Displays this message"), + new InternalActionOption("set", "Set a setting"), + new InternalActionOption("remove", "Remove a setting"), + new InternalActionOption("add", "Add a setting") + }; public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public Task Execute(string[] args) { @@ -19,7 +28,7 @@ public class SettingsConfig : ICommandAction { foreach (var settings in Config.AppSettings) Console.WriteLine(settings.Key + ": " + settings.Value); - + return Task.CompletedTask; } @@ -27,25 +36,25 @@ public class SettingsConfig : ICommandAction { case "-s": case "set": - if(args.Length < 3) + if (args.Length < 3) return Task.CompletedTask; - SettingsConfigExtra.SetSettings(args[1],args[2..]); + SettingsConfigExtra.SetSettings(args[1], args[2..]); break; - + case "-r": case "remove": - if(args.Length < 2) + if (args.Length < 2) return Task.CompletedTask; SettingsConfigExtra.RemoveSettings(args[1]); break; - + case "-a": case "add": - if(args.Length < 3) + if (args.Length < 3) return Task.CompletedTask; SettingsConfigExtra.AddSettings(args[1], args[2..]); break; - + case "-h": case "-help": Console.WriteLine("Options:"); @@ -54,14 +63,14 @@ public class SettingsConfig : ICommandAction Console.WriteLine("-a : Add a setting"); Console.WriteLine("-h: Show this help message"); break; - + default: Console.WriteLine("Invalid option"); return Task.CompletedTask; } - - - + + + return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/DiscordBot/Bot/Commands/NormalCommands/Help.cs b/DiscordBot/Bot/Commands/NormalCommands/Help.cs index 0fa3256..c45d2a0 100644 --- a/DiscordBot/Bot/Commands/NormalCommands/Help.cs +++ b/DiscordBot/Bot/Commands/NormalCommands/Help.cs @@ -10,7 +10,7 @@ namespace DiscordBot.Bot.Commands; /// /// The help command /// -internal class Help : DBCommand +internal class Help: DBCommand { /// /// Command name @@ -38,7 +38,7 @@ internal class Help : DBCommand /// The main body of the command /// /// The command context - public void ExecuteServer(DBCommandExecutingArguments args) + public void ExecuteServer(DbCommandExecutingArguments args) { if (args.arguments is not null) { @@ -76,7 +76,7 @@ internal class Help : DBCommand var embedBuilder = new EmbedBuilder(); var cmd = PluginLoader.Commands.Find(p => p.Command == command || p.Aliases is not null && p.Aliases.Contains(command) - ); + ); if (cmd == null) return null; embedBuilder.AddField("Usage", Config.AppSettings["prefix"] + cmd.Usage); diff --git a/DiscordBot/Bot/Commands/SlashCommands/Help.cs b/DiscordBot/Bot/Commands/SlashCommands/Help.cs index b733818..b36a443 100644 --- a/DiscordBot/Bot/Commands/SlashCommands/Help.cs +++ b/DiscordBot/Bot/Commands/SlashCommands/Help.cs @@ -8,11 +8,13 @@ using PluginManager.Others; namespace DiscordBot.Bot.Commands.SlashCommands; -public class Help : DBSlashCommand +public class Help: DBSlashCommand { - public string Name => "help"; + public string Name => "help"; public string Description => "This command allows you to check all loaded commands"; - public bool canUseDM => true; + public bool canUseDM => true; + + public bool HasInteraction => false; public List Options => new() @@ -31,7 +33,7 @@ public class Help : DBSlashCommand embedBuilder.WithTitle("Help Command"); embedBuilder.WithColor(Functions.RandomColor); var slashCommands = PluginLoader.SlashCommands; - var options = context.Data.Options; + var options = context.Data.Options; //Console.WriteLine("Options: " + options.Count); if (options is null || options.Count == 0) @@ -40,15 +42,15 @@ public class Help : DBSlashCommand if (options.Count > 0) { - var commandName = options.First().Name; - var slashCommand = slashCommands.FirstOrDefault(x => x.Name == commandName); + var commandName = options.First().Value; + var slashCommand = slashCommands.FirstOrDefault(x => x.Name.TrimEnd() == commandName.ToString()); if (slashCommand is null) { await context.RespondAsync("Unknown Command " + commandName); return; } - embedBuilder.AddField(slashCommand.Name, slashCommand.canUseDM) + embedBuilder.AddField("DM Usable:", slashCommand.canUseDM, true) .WithDescription(slashCommand.Description); } diff --git a/DiscordBot/DiscordBot.csproj b/DiscordBot/DiscordBot.csproj index 82c846f..633e99b 100644 --- a/DiscordBot/DiscordBot.csproj +++ b/DiscordBot/DiscordBot.csproj @@ -1,14 +1,13 @@ Exe - net6.0 - enable - - + net8.0 + disable + + False True 1.0.3.2 - 10 none @@ -17,31 +16,26 @@ none - - - - + + + - - - - + + + - - - + + + - - - - + - + \ No newline at end of file diff --git a/DiscordBot/Entry.cs b/DiscordBot/Entry.cs index e3056be..2451247 100644 --- a/DiscordBot/Entry.cs +++ b/DiscordBot/Entry.cs @@ -2,30 +2,45 @@ using System.IO; using System.Reflection; + namespace DiscordBot; public static class Entry { + private static readonly string logo = @" + _____ _ _ _____ _ _ ____ _ + / ____| | | | | | __ \(_) | | | _ \ | | + | (___ ___| |_| |__ | | | |_ ___ ___ ___ _ __ __| | | |_) | ___ | |_ + \___ \ / _ \ __| '_ \ | | | | / __|/ __/ _ \| '__/ _` | | _ < / _ \| __| + ____) | __/ |_| | | | | |__| | \__ \ (_| (_) | | | (_| | | |_) | (_) | |_ + |_____/ \___|\__|_| |_| |_____/|_|___/\___\___/|_| \__,_| |____/ \___/ \__| + + +"; public static void Main(string[] args) { #if DEBUG - if (args.Length == 1 && args[0] == "/purge_plugins") + if (args.Length == 1 && args[0] == "/purge_plugins" ) { foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories)) { File.Delete(plugin); } } - + #endif - - + + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine(logo); + Console.ResetColor(); + + var currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve += LoadFromSameFolder; static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) { - var folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "./Libraries"); + var folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "./Libraries"); var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); if (!File.Exists(assemblyPath)) return null; var assembly = Assembly.LoadFrom(assemblyPath); diff --git a/DiscordBot/Installer.cs b/DiscordBot/Installer.cs index 1e27dc9..d38381e 100644 --- a/DiscordBot/Installer.cs +++ b/DiscordBot/Installer.cs @@ -1,28 +1,39 @@ +using System; using PluginManager; +using System.Threading.Tasks; using Spectre.Console; namespace DiscordBot; public static class Installer { - public static void GenerateStartupConfig() + private static async Task AskForConfig(string key, string message) { - AnsiConsole.MarkupLine("Welcome to the [bold]SethBot[/] installer !"); - AnsiConsole.MarkupLine("First, we need to configure the bot. Don't worry, it will be quick !"); - - var token = AnsiConsole.Ask("Please enter the bot [yellow]token[/]:"); - var prefix = AnsiConsole.Ask("Please enter the bot [yellow]prefix[/]:"); - var serverId = AnsiConsole.Ask("Please enter the [yellow]Server ID[/]:"); + var value = AnsiConsole.Ask($"[green]{message}[/]"); - if (string.IsNullOrWhiteSpace(serverId)) serverId = "NULL"; - Config.AppSettings.Add("token", token); - Config.AppSettings.Add("prefix", prefix); - Config.AppSettings.Add("ServerID", serverId); + if (string.IsNullOrWhiteSpace(value)) + { + AnsiConsole.MarkupLine($"Invalid {key} !"); - Config.AppSettings.SaveToFile(); - - AnsiConsole.MarkupLine("[bold]Config saved ![/]"); - - Config.Logger.Log("Config Saved", source: typeof(Installer)); + Environment.Exit(-20); + } + + Config.AppSettings.Add(key, value); + } + public static async Task GenerateStartupConfig() + { + + if(!Config.AppSettings.ContainsKey("token")) + await AskForConfig("token", "Token:"); + + if(!Config.AppSettings.ContainsKey("prefix")) + await AskForConfig("prefix", "Prefix:"); + + if(!Config.AppSettings.ContainsKey("ServerID")) + await AskForConfig("ServerID", "Server ID:"); + + await Config.AppSettings.SaveToFile(); + + Config.Logger.Log("Config Saved", typeof(Installer)); } } diff --git a/DiscordBot/Program.cs b/DiscordBot/Program.cs index 818b4b7..fb3eddd 100644 --- a/DiscordBot/Program.cs +++ b/DiscordBot/Program.cs @@ -22,9 +22,9 @@ public class Program public static void Startup(string[] args) { PreLoadComponents(args).Wait(); - + if (!AppSettings.ContainsKey("ServerID") || !AppSettings.ContainsKey("token") || !AppSettings.ContainsKey("prefix")) - Installer.GenerateStartupConfig(); + Installer.GenerateStartupConfig().Wait(); HandleInput().Wait(); } @@ -37,8 +37,7 @@ public class Program internalActionManager.Initialize().Wait(); internalActionManager.Execute("plugin", "load").Wait(); internalActionManager.Refresh().Wait(); - - + while (true) { var cmd = Console.ReadLine(); @@ -55,59 +54,51 @@ public class Program /// /// Start the bot without user interface /// - /// Returns the boot loader for the Discord Bot + /// Returns the bootloader for the Discord Bot private static async Task StartNoGui() { - Console.Clear(); - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}"); - Console.WriteLine("Git SethBot: https://github.com/andreitdr/SethDiscordBot"); - Console.WriteLine("Git Plugins: https://github.com/andreitdr/SethPlugins"); - - ConsoleUtilities.WriteColorText("&rRemember to close the bot using the ShutDown command (&yexit&r) or some settings won't be saved"); + AnsiConsole.MarkupLine($"[yellow]Running on version: {AppSettings["Version"]}[/]"); + AnsiConsole.MarkupLine("[yellow]Git SethBot: https://github.com/andreitdr/SethDiscordBot [/]"); + AnsiConsole.MarkupLine("[yellow]Git Plugins: https://github.com/andreitdr/SethPlugins [/]"); + + AnsiConsole.MarkupLine("[yellow]Remember to close the bot using the shutdown command ([/][red]exit[/][yellow]) or some settings won't be saved[/]"); + AnsiConsole.MarkupLine($"[yellow]Running on [/][magenta]{(OperatingSystem.IsWindows() ? "Windows" : "Linux")}[/]"); + + AnsiConsole.MarkupLine("[yellow]===== Seth Discord Bot =====[/]"); - ConsoleUtilities.WriteColorText($"Running on &m{Functions.GetOperatingSystem()}"); - Console.WriteLine("============================ LOG ============================"); - - Console.ForegroundColor = ConsoleColor.White; try { - var token = AppSettings["token"]; + var token = AppSettings["token"]; var prefix = AppSettings["prefix"]; var discordbooter = new Boot(token, prefix); await discordbooter.Awake(); } - catch ( Exception ex ) + catch (Exception ex) { - Logger.Log(ex.ToString(), source: typeof(Program), type: LogType.CRITICAL); + Logger.Log(ex.ToString(), typeof(Program), LogType.CRITICAL); } } /// /// Handle user input arguments from the startup of the application /// - /// The arguments private static async Task HandleInput() { await StartNoGui(); try { - internalActionManager = new InternalActionManager("./Data/Plugins", "*.dll"); + internalActionManager = new InternalActionManager(AppSettings["PluginFolder"], "*.dll"); NoGUI(); } - catch ( IOException ex ) + catch (IOException ex) { if (ex.Message == "No process is on the other end of the pipe." || (uint)ex.HResult == 0x800700E9) { - if (AppSettings.ContainsKey("LaunchMessage")) - AppSettings.Add("LaunchMessage", - "An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" + - "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !"); - Logger.Log("An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" + - "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !", - source: typeof(Program), type: LogType.ERROR); + "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !", + typeof(Program), LogType.ERROR + ); } } } @@ -115,10 +106,24 @@ public class Program private static async Task PreLoadComponents(string[] args) { await Initialize(); - + + AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + PluginManager.Updater.Application.AppUpdater updater = new(); + var update = await updater.CheckForUpdates(); + + if (update != PluginManager.Updater.Application.Update.None) + { + Console.WriteLine($"New update available: {update.UpdateVersion}"); + Console.WriteLine($"Download link: {update.UpdateUrl}"); + Console.WriteLine($"Update notes: {update.UpdateNotes}\n\n"); + + Environment.Exit(0); + } + Logger.OnLog += (sender, logMessage) => { - string messageColor = logMessage.Type switch + var messageColor = logMessage.Type switch { LogType.INFO => "[green]", LogType.WARNING => "[yellow]", @@ -129,14 +134,11 @@ public class Program if (logMessage.Message.Contains('[')) { - // If the message contains a tag, just print it as it is. No need to format it Console.WriteLine(logMessage.Message); return; } - + AnsiConsole.MarkupLine($"{messageColor}{logMessage.ThrowTime} {logMessage.Message} [/]"); }; - - AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } diff --git a/DiscordBot/Utilities/Console Utilities.cs b/DiscordBot/Utilities/Console Utilities.cs index 5fdbe60..45b0eed 100644 --- a/DiscordBot/Utilities/Console Utilities.cs +++ b/DiscordBot/Utilities/Console Utilities.cs @@ -7,336 +7,25 @@ using Spectre.Console; namespace DiscordBot.Utilities; -public class TableData -{ - public List Columns; - public List Rows; - - public bool IsEmpty => Rows.Count == 0; - public bool HasRoundBorders { get; set; } = true; - - public TableData(List columns) - { - Columns = columns; - Rows = new List(); - } - - public TableData(string[] columns) - { - Columns = columns.ToList(); - Rows = new List(); - } - - public void AddRow(string[] row) - { - Rows.Add(row); - } -} - public static class ConsoleUtilities { - public static async Task ExecuteWithProgressBar(Task function, string message) { T result = default; await AnsiConsole.Progress() - .Columns( - new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn(), - } - ) - .StartAsync( - async ctx => - { - var task = ctx.AddTask(message); - task.IsIndeterminate = true; - result = await function; - task.Increment(100); + .AutoClear(true) + .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()) + .StartAsync( + async ctx => + { + var task = ctx.AddTask(message); + task.IsIndeterminate = true; + result = await function; + task.Increment(100); + } + ); - } - ); return result; } - - public static async Task ExecuteWithProgressBar(Task function, string message) - { - await AnsiConsole.Progress() - .Columns(new ProgressColumn[] - { - new TaskDescriptionColumn(), - new ProgressBarColumn(), - new PercentageColumn(), - }) - .StartAsync(async ctx => - { - var task = ctx.AddTask(message); - task.IsIndeterminate = true; - await function; - task.Increment(100); - - }); - } - - private static readonly Dictionary Colors = new() - { - { 'g', ConsoleColor.Green }, - { 'b', ConsoleColor.Blue }, - { 'r', ConsoleColor.Red }, - { 'm', ConsoleColor.Magenta }, - { 'y', ConsoleColor.Yellow } - }; - - private static readonly char ColorPrefix = '&'; - - private static bool CanAproximateTo(this float f, float y) - { - return MathF.Abs(f - y) < 0.000001; - } - - public static void PrintAsTable(this TableData tableData) - { - var table = new Table(); - table.Border(tableData.HasRoundBorders ? TableBorder.Rounded : TableBorder.Square); - table.AddColumns(tableData.Columns.ToArray()); - foreach (var row in tableData.Rows) - table.AddRow(row); - - AnsiConsole.Write(table); - } - - - /// - /// A way to create a table based on input data - /// - /// The List of arrays of string that represent the rows. - public static void FormatAndAlignTable(List data, TableFormat format) - { - if (format == TableFormat.SPECTRE_CONSOLE) - { - var table = new Table(); - table.Border(TableBorder.Rounded); - table.AddColumns(data[0]); - data.RemoveAt(0); - foreach (var row in data) - table.AddRow(row); - - AnsiConsole.Write(table); - - return; - } - - if (format == TableFormat.CENTER_EACH_COLUMN_BASED) - { - var tableLine = '-'; - var tableCross = '+'; - var tableWall = '|'; - - var len = new int[data[0].Length]; - foreach (var line in data) - for (var i = 0; i < line.Length; i++) - if (line[i].Length > len[i]) - len[i] = line[i].Length; - - - foreach (var row in data) - { - if (row[0][0] == tableLine) - Console.Write(tableCross); - else - Console.Write(tableWall); - for (var l = 0; l < row.Length; l++) - { - if (row[l][0] == tableLine) - { - for (var i = 0; i < len[l] + 4; ++i) - Console.Write(tableLine); - } - else if (row[l].Length == len[l]) - { - Console.Write(" "); - Console.Write(row[l]); - Console.Write(" "); - } - else - { - var lenHalf = row[l].Length / 2; - for (var i = 0; i < (len[l] + 4) / 2 - lenHalf; ++i) - Console.Write(" "); - Console.Write(row[l]); - for (var i = (len[l] + 4) / 2 + lenHalf + 1; i < len[l] + 4; ++i) - Console.Write(" "); - if (row[l].Length % 2 == 0) - Console.Write(" "); - } - - Console.Write(row[l][0] == tableLine ? tableCross : tableWall); - } - - Console.WriteLine(); //end line - } - - return; - } - - if (format == TableFormat.CENTER_OVERALL_LENGTH) - { - var maxLen = 0; - foreach (var row in data) - foreach (var s in row) - if (s.Length > maxLen) - maxLen = s.Length; - - var div = (maxLen + 4) / 2; - - foreach (var row in data) - { - Console.Write("\t"); - if (row[0] == "-") - Console.Write("+"); - else - Console.Write("|"); - - foreach (var s in row) - { - if (s == "-") - { - for (var i = 0; i < maxLen + 4; ++i) - Console.Write("-"); - } - else if (s.Length == maxLen) - { - Console.Write(" "); - Console.Write(s); - Console.Write(" "); - } - else - { - var lenHalf = s.Length / 2; - for (var i = 0; i < div - lenHalf; ++i) - Console.Write(" "); - Console.Write(s); - for (var i = div + lenHalf + 1; i < maxLen + 4; ++i) - Console.Write(" "); - if (s.Length % 2 == 0) - Console.Write(" "); - } - - if (s == "-") - Console.Write("+"); - else - Console.Write("|"); - } - - Console.WriteLine(); //end line - } - - return; - } - - if (format == TableFormat.DEFAULT) - { - var widths = new int[data[0].Length]; - var space_between_columns = 3; - for (var i = 0; i < data.Count; i++) - for (var j = 0; j < data[i].Length; j++) - if (data[i][j].Length > widths[j]) - widths[j] = data[i][j].Length; - - for (var i = 0; i < data.Count; i++) - { - for (var j = 0; j < data[i].Length; j++) - { - if (data[i][j] == "-") - data[i][j] = " "; - Console.Write(data[i][j]); - for (var k = 0; k < widths[j] - data[i][j].Length + 1 + space_between_columns; k++) - Console.Write(" "); - } - - Console.WriteLine(); - } - - return; - } - - throw new Exception("Unknown type of table"); - } - - public static void WriteColorText(string text, bool appendNewLineAtEnd = true) - { - var initialForeGround = Console.ForegroundColor; - var input = text.ToCharArray(); - for (var i = 0; i < input.Length; i++) - if (input[i] == ColorPrefix) - { - if (i + 1 < input.Length) - { - if (Colors.ContainsKey(input[i + 1])) - { - Console.ForegroundColor = Colors[input[i + 1]]; - i++; - } - else if (input[i + 1] == 'c') - { - Console.ForegroundColor = initialForeGround; - i++; - } - } - } - else - { - Console.Write(input[i]); - } - - Console.ForegroundColor = initialForeGround; - if (appendNewLineAtEnd) - Console.WriteLine(); - } - - - public class Spinner - { - private readonly string[] Sequence; - private bool isRunning; - public string Message; - private int position; - private Thread thread; - - public Spinner() - { - Sequence = new[] { "|", "/", "-", "\\" }; - position = 0; - } - - public void Start() - { - Console.CursorVisible = false; - isRunning = true; - thread = new Thread(() => - { - while (isRunning) - { - Console.SetCursorPosition(0, Console.CursorTop); - Console.Write(" " + Sequence[position] + " " + Message + " "); - position++; - if (position >= Sequence.Length) - position = 0; - Thread.Sleep(100); - } - } - ); - - thread.Start(); - } - - public void Stop() - { - isRunning = false; - Console.CursorVisible = true; - } - } -} +} \ No newline at end of file diff --git a/DiscordBot/Utilities/Enums.cs b/DiscordBot/Utilities/Enums.cs deleted file mode 100644 index 892a255..0000000 --- a/DiscordBot/Utilities/Enums.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace DiscordBot.Utilities; - -public enum TableFormat -{ - SPECTRE_CONSOLE, - CENTER_EACH_COLUMN_BASED, - CENTER_OVERALL_LENGTH, - DEFAULT -} diff --git a/DiscordBot/Utilities/TableData.cs b/DiscordBot/Utilities/TableData.cs new file mode 100644 index 0000000..27b1e34 --- /dev/null +++ b/DiscordBot/Utilities/TableData.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading.Tasks; +using PluginManager.Others; +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace DiscordBot.Utilities +{ + public class TableData + { + public List Columns; + public List[]> Rows; + + public TableData() + { + Columns = new List(); + Rows = new List[]>(); + } + + public TableData(List columns) + { + Columns = columns; + Rows = new List[]>(); + } + + public bool IsEmpty => Rows.Count == 0; + public bool HasRoundBorders { get; set; } = true; + public bool DisplayLinesBetweenRows { get; set; } = false; + + public void AddRow(OneOf[] row) + { + Rows.Add(row); + } + + public void PrintTable() + { + var table = new Table(); + table.Border(this.HasRoundBorders ? TableBorder.Rounded : TableBorder.Square); + table.AddColumns(this.Columns.ToArray()); + table.ShowRowSeparators = DisplayLinesBetweenRows; + foreach (var row in this.Rows) + { + table.AddRow(row.Select(element => element.Match( + (data) => new Markup(data), + (data) => data + ))); + } + + AnsiConsole.Write(table); + } + } +} diff --git a/DiscordBotUI/.gitignore b/DiscordBotUI/.gitignore new file mode 100644 index 0000000..8afdcb6 --- /dev/null +++ b/DiscordBotUI/.gitignore @@ -0,0 +1,454 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/DiscordBotUI/DiscordBotUI.Desktop/DiscordBotUI.Desktop.csproj b/DiscordBotUI/DiscordBotUI.Desktop/DiscordBotUI.Desktop.csproj new file mode 100644 index 0000000..6befdf1 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI.Desktop/DiscordBotUI.Desktop.csproj @@ -0,0 +1,24 @@ + + + WinExe + + net8.0 + enable + true + + + + app.manifest + + + + + + + + + + + + diff --git a/DiscordBotUI/DiscordBotUI.Desktop/Program.cs b/DiscordBotUI/DiscordBotUI.Desktop/Program.cs new file mode 100644 index 0000000..0d660fb --- /dev/null +++ b/DiscordBotUI/DiscordBotUI.Desktop/Program.cs @@ -0,0 +1,25 @@ +using System; + +using Avalonia; +using Avalonia.ReactiveUI; + +namespace DiscordBotUI.Desktop +{ + internal sealed class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); + } +} diff --git a/DiscordBotUI/DiscordBotUI.Desktop/Properties/launchSettings.json b/DiscordBotUI/DiscordBotUI.Desktop/Properties/launchSettings.json new file mode 100644 index 0000000..69f26c5 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI.Desktop/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "DiscordBotUI.Desktop": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/DiscordBotUI/DiscordBotUI.Desktop/app.manifest b/DiscordBotUI/DiscordBotUI.Desktop/app.manifest new file mode 100644 index 0000000..1a06515 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/DiscordBotUI/DiscordBotUI.Desktop/builder.bat b/DiscordBotUI/DiscordBotUI.Desktop/builder.bat new file mode 100644 index 0000000..f73d6b4 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI.Desktop/builder.bat @@ -0,0 +1,35 @@ +@echo off +echo "Building..." + +echo "Building linux-x64 not self-contained" +dotnet publish -r linux-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/linux-x64 + +echo "Building win-x64 not self-contained" +dotnet publish -r win-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/win-x64 + +echo "Building osx-x64 not self-contained" +dotnet publish -r osx-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/osx-x64 + + +echo "Building linux-x64 self-contained" +dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/linux-x64-selfcontained + +echo "Building win-x64 self-contained" +dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/win-x64-selfcontained + +echo "Building osx-x64 self-contained" +dotnet publish -r osx-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/osx-x64-selfcontained + +echo "Zipping..." +mkdir ../publish/zip + + +zip -r ../publish/zip/linux-x64.zip ../publish/linux-x64 +zip -r ../publish/zip/win-x64.zip ../publish/win-x64 +zip -r ../publish/zip/osx-x64.zip ../publish/osx-x64 + +zip -r ../publish/zip/linux-x64-selfcontained.zip ../publish/linux-x64-selfcontained +zip -r ../publish/zip/win-x64-selfcontained.zip ../publish/win-x64-selfcontained +zip -r ../publish/zip/osx-x64-selfcontained.zip ../publish/osx-x64-selfcontained + +echo "Done!" \ No newline at end of file diff --git a/DiscordBotUI/DiscordBotUI/App.axaml b/DiscordBotUI/DiscordBotUI/App.axaml new file mode 100644 index 0000000..c9d9a69 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/App.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/DiscordBotUI/DiscordBotUI/App.axaml.cs b/DiscordBotUI/DiscordBotUI/App.axaml.cs new file mode 100644 index 0000000..5194606 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/App.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +using DiscordBotUI.ViewModels; +using DiscordBotUI.Views; + +namespace DiscordBotUI +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new HomePage(); + } + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + singleViewPlatform.MainView = new HomePage(); + } + + base.OnFrameworkInitializationCompleted(); + } + } +} \ No newline at end of file diff --git a/DiscordBotUI/DiscordBotUI/Assets/avalonia-logo.ico b/DiscordBotUI/DiscordBotUI/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/DiscordBotUI/DiscordBotUI/Assets/avalonia-logo.ico differ diff --git a/DiscordBotUI/DiscordBotUI/Bot/Commands/Help.cs b/DiscordBotUI/DiscordBotUI/Bot/Commands/Help.cs new file mode 100644 index 0000000..8f9ec34 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/Bot/Commands/Help.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using Discord; +using PluginManager; +using PluginManager.Interfaces; +using PluginManager.Loaders; +using PluginManager.Others; + +namespace DiscordBotUI.Bot.Commands; + +/// +/// The help command +/// +internal class Help: DBCommand +{ + /// + /// Command name + /// + public string Command => "help"; + + public List Aliases => null; + + /// + /// Command Description + /// + public string Description => "This command allows you to check all loaded commands"; + + /// + /// Command usage + /// + public string Usage => "help "; + + /// + /// Check if the command require administrator to be executed + /// + public bool requireAdmin => false; + + /// + /// The main body of the command + /// + /// The command context + public void ExecuteServer(DbCommandExecutingArguments args) + { + if (args.arguments is not null) + { + var e = GenerateHelpCommand(args.arguments[0]); + if (e is null) + args.context.Channel.SendMessageAsync("Unknown Command " + args.arguments[0]); + else + args.context.Channel.SendMessageAsync(embed: e.Build()); + + + return; + } + + var embedBuilder = new EmbedBuilder(); + + var adminCommands = ""; + var normalCommands = ""; + + foreach (var cmd in PluginLoader.Commands) + if (cmd.requireAdmin) + adminCommands += cmd.Command + " "; + else + normalCommands += cmd.Command + " "; + + + if (adminCommands.Length > 0) + embedBuilder.AddField("Admin Commands", adminCommands); + if (normalCommands.Length > 0) + embedBuilder.AddField("Normal Commands", normalCommands); + args.context.Channel.SendMessageAsync(embed: embedBuilder.Build()); + } + + private EmbedBuilder GenerateHelpCommand(string command) + { + var embedBuilder = new EmbedBuilder(); + var cmd = PluginLoader.Commands.Find(p => p.Command == command || + p.Aliases is not null && p.Aliases.Contains(command) + ); + if (cmd == null) return null; + + embedBuilder.AddField("Usage", Config.AppSettings["prefix"] + cmd.Usage); + embedBuilder.AddField("Description", cmd.Description); + if (cmd.Aliases is null) + return embedBuilder; + embedBuilder.AddField("Alias", cmd.Aliases.Count == 0 ? "-" : string.Join(", ", cmd.Aliases)); + + return embedBuilder; + } +} diff --git a/DiscordBotUI/DiscordBotUI/Bot/DiscordBot.cs b/DiscordBotUI/DiscordBotUI/Bot/DiscordBot.cs new file mode 100644 index 0000000..1e7ed32 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/Bot/DiscordBot.cs @@ -0,0 +1,83 @@ +using System.Threading.Tasks; + +using PluginManager; +using PluginManager.Interfaces; +using PluginManager.Loaders; +using PluginManager.Others; + +namespace DiscordBotUI.Bot +{ + internal class DiscordBot + { + private readonly string[] _StartArguments; + + public DiscordBot(string[] args) + { + this._StartArguments = args; + } + + public async Task InitializeBot() + { + string token = Config.AppSettings["token"]; + string prefix = Config.AppSettings["prefix"]; + PluginManager.Bot.Boot discordBooter = new PluginManager.Bot.Boot(token, prefix); + await discordBooter.Awake(); + } + + public async Task LoadPlugins() + { + var loader = new PluginLoader(Config.DiscordBot.Client); + + loader.OnCommandLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded command : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } + + else + { + Config.Logger.Log("Failed to load command : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } + }; + loader.OnEventLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded event : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } + else + { + Config.Logger.Log("Failed to load event : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } + }; + + loader.OnSlashCommandLoaded += (data) => + { + if (data.IsSuccess) + { + Config.Logger.Log("Successfully loaded slash command : " + data.PluginName, typeof(ICommandAction), + LogType.INFO + ); + } + else + { + Config.Logger.Log("Failed to load slash command : " + data.PluginName + " because " + data.ErrorMessage, + typeof(ICommandAction), LogType.ERROR + ); + } + }; + + await loader.LoadPlugins(); + } + + } +} diff --git a/DiscordBotUI/DiscordBotUI/DiscordBotUI.csproj b/DiscordBotUI/DiscordBotUI/DiscordBotUI.csproj new file mode 100644 index 0000000..9ceec94 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/DiscordBotUI.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + latest + true + + + + + + + + + + + + + + + + + + + diff --git a/DiscordBotUI/DiscordBotUI/ViewLocator.cs b/DiscordBotUI/DiscordBotUI/ViewLocator.cs new file mode 100644 index 0000000..c72ec8c --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/ViewLocator.cs @@ -0,0 +1,33 @@ +using System; + +using Avalonia.Controls; +using Avalonia.Controls.Templates; + +using DiscordBotUI.ViewModels; + +namespace DiscordBotUI +{ + public class ViewLocator : IDataTemplate + { + public Control? Build(object? data) + { + if (data is null) + return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } + } +} \ No newline at end of file diff --git a/DiscordBotUI/DiscordBotUI/ViewModels/OnlinePlugin.cs b/DiscordBotUI/DiscordBotUI/ViewModels/OnlinePlugin.cs new file mode 100644 index 0000000..0545e38 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/ViewModels/OnlinePlugin.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DiscordBotUI.ViewModels +{ + public class OnlinePlugin + { + public string Name { get; set; } + public string Description { get; set; } + public string Version { get; set; } + + public OnlinePlugin(string name, string description, string version) { + Name = name; + Description = description; + Version = version; + + } + } +} diff --git a/DiscordBotUI/DiscordBotUI/ViewModels/Plugin.cs b/DiscordBotUI/DiscordBotUI/ViewModels/Plugin.cs new file mode 100644 index 0000000..55bb941 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/ViewModels/Plugin.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DiscordBotUI.ViewModels +{ + public class Plugin + { + public string Name { get; set; } + public string Version { get; set; } + public bool IsMarkedToUninstall { get; set; } + + public Plugin(string Name, string Version, bool isMarkedToUninstall) + { + this.Name = Name; + this.Version = Version; + IsMarkedToUninstall = isMarkedToUninstall; + } + } +} diff --git a/DiscordBotUI/DiscordBotUI/ViewModels/ViewModelBase.cs b/DiscordBotUI/DiscordBotUI/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..6d48d97 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/ViewModels/ViewModelBase.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +using ReactiveUI; + +namespace DiscordBotUI.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/DiscordBotUI/DiscordBotUI/Views/HomePage.axaml b/DiscordBotUI/DiscordBotUI/Views/HomePage.axaml new file mode 100644 index 0000000..f783e49 --- /dev/null +++ b/DiscordBotUI/DiscordBotUI/Views/HomePage.axaml @@ -0,0 +1,41 @@ + + + + + + + + + + + +