diff --git a/DiscordBot/Bot/Actions/AddPlugin.cs b/DiscordBot/Bot/Actions/AddPlugin.cs deleted file mode 100644 index 3e6c52a..0000000 --- a/DiscordBot/Bot/Actions/AddPlugin.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; -using DiscordBotCore.Plugin; - -namespace DiscordBot.Bot.Actions -{ - internal class AddPlugin : ICommandAction - { - public string ActionName => "add-plugin"; - - public string Description => "Add a local plugin to the database"; - - public string Usage => "add-plugin "; - - public IEnumerable ListOfOptions => [ - new InternalActionOption("options", "Available options", [ - new InternalActionOption("-enabled", "Enable the plugin"), - ]), - new InternalActionOption("fileName", "The file name") - ]; - - public InternalActionRunType RunType => InternalActionRunType.OnCall; - - public bool RequireOtherThread => false; - - public async Task Execute(string[] args) - { - if(args.Length < 1) - { - Application.CurrentApplication.Logger.Log("Incorrect number of arguments !", LogType.Warning); - return; - } - - string fileName = args[^1] + ".dll"; - var path = Application.GetPluginFullPath(fileName); - - if(!File.Exists(path)) - { - Application.CurrentApplication.Logger.Log("The file does not exist !!", LogType.Error); - return; - } - - if (args[^1] is null) - { - Application.CurrentApplication.Logger.Log("The plugin name is invalid", LogType.Error); - } - - PluginInfo pluginInfo = new PluginInfo(args[^1], "1.0.0", [], false, true, args.Contains("-enabled")); - Application.CurrentApplication.Logger.Log("Adding plugin: " + args[^1]); - await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(pluginInfo); - } - } -} diff --git a/DiscordBot/Bot/Actions/Clear.cs b/DiscordBot/Bot/Actions/Clear.cs deleted file mode 100644 index d6e6be5..0000000 --- a/DiscordBot/Bot/Actions/Clear.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; - -using System.Threading.Tasks; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Actions; - -public class Clear: ICommandAction -{ - public string ActionName => "clear"; - public string Description => "Clears the console"; - public string Usage => "clear"; - public IEnumerable ListOfOptions => []; - - public InternalActionRunType RunType => InternalActionRunType.OnCall; - - public bool RequireOtherThread => false; - - public Task Execute(string[] args) - { - Console.Clear(); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("===== Seth Discord Bot ====="); - Console.ResetColor(); - return Task.CompletedTask; - } -} diff --git a/DiscordBot/Bot/Actions/Exit.cs b/DiscordBot/Bot/Actions/Exit.cs deleted file mode 100644 index b2e3896..0000000 --- a/DiscordBot/Bot/Actions/Exit.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Actions; - -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 "; - 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.OnCall; - - public bool RequireOtherThread => false; - - public async Task Execute(string[] args) - { - if (args is null || args.Length == 0) - { - Application.CurrentApplication.Logger.Log("Exiting...", this, LogType.Warning); - await Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile(); - Environment.Exit(0); - } - else - { - switch (args[0]) - { - case "help": - Console.WriteLine("Usage : exit [help|force]"); - Console.WriteLine("help : Displays this message"); - Console.WriteLine("force | -f : Exits the bot without saving the config"); - break; - - case "-f": - case "force": - Application.CurrentApplication.Logger.Log("Exiting (FORCE)...", this, LogType.Warning); - Environment.Exit(0); - break; - - default: - Console.WriteLine("Invalid argument !"); - break; - } - } - } -} diff --git a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs b/DiscordBot/Bot/Actions/Extra/PluginMethods.cs deleted file mode 100644 index bf225da..0000000 --- a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -using DiscordBot.Utilities; - -using DiscordBotCore; -using DiscordBotCore.Loaders; -using DiscordBotCore.Online; -using DiscordBotCore.Others; -using DiscordBotCore.Plugin; - -using Spectre.Console; - -namespace DiscordBot.Bot.Actions.Extra; - -internal static class PluginMethods -{ - - internal static async Task List() - { - Console.WriteLine($"Fetching plugin list ..."); - - var onlinePlugins = await ConsoleUtilities.ExecuteWithProgressBar(Application.CurrentApplication.PluginManager.GetPluginsList(), "Reading remote database"); - var installedPlugins = await ConsoleUtilities.ExecuteWithProgressBar(Application.CurrentApplication.PluginManager.GetInstalledPlugins(), "Reading local database "); - TableData tableData = new(["Name", "Description", "Author", "Latest Version", "Installed Version"]); - - foreach (var onlinePlugin in onlinePlugins) - { - bool isInstalled = installedPlugins.Any(p => p.PluginName == onlinePlugin.PluginName); - tableData.AddRow([ - onlinePlugin.PluginName, - onlinePlugin.PluginDescription, - onlinePlugin.PluginAuthor, - onlinePlugin.LatestVersion, - isInstalled ? installedPlugins.First(p => p.PluginName == onlinePlugin.PluginName).PluginVersion : "Not Installed" - ]); - } - - tableData.HasRoundBorders = false; - tableData.DisplayLinesBetweenRows = true; - tableData.PrintTable(); - } - - internal static async Task RefreshPlugins(bool quiet) - { - try - { - await LoadPlugins(quiet ? ["-q"] : null); - - }catch(Exception ex) - { - Application.CurrentApplication.Logger.LogException(ex, typeof(PluginMethods), false); - } finally - { - await Application.CurrentApplication.InternalActionManager.Initialize(); - } - - } - - internal static async Task DisablePlugin(string pluginName) - { - await Application.CurrentApplication.PluginManager.SetEnabledStatus(pluginName, false); - } - - internal static async Task EnablePlugin(string pluginName) - { - await Application.CurrentApplication.PluginManager.SetEnabledStatus(pluginName, true); - } - - public static async Task DownloadPluginWithParallelDownloads(string pluginName) - { - OnlinePlugin? pluginData = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName); - - if (pluginData is null) - { - Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again."); - return; - } - - var result = await Application.CurrentApplication.PluginManager.GatherInstallDataForPlugin(pluginData); - List> downloadList = result.Item1.Select(kvp => new Tuple(kvp.Key, kvp.Value)).ToList(); - - await ConsoleUtilities.ExecuteParallelDownload(FileDownloader.CreateDownloadTask, new HttpClient(), downloadList, "Downloading:"); - - await Application.CurrentApplication.PluginManager.AppendPluginToDatabase(PluginInfo.FromOnlineInfo(pluginData, result.Item2)); - } - - - internal static async Task LoadPlugins(string[] args) - { - var loader = new PluginLoader(Application.CurrentApplication.DiscordBotClient.Client); - if (args != null && args.Contains("-q")) - { - await loader.LoadPlugins(); - return true; - } - - loader.OnCommandLoaded += (command) => - { - Application.CurrentApplication.Logger.Log($"Command {command.Command} loaded successfully", LogType.Info); - }; - - loader.OnEventLoaded += (eEvent) => - { - Application.CurrentApplication.Logger.Log($"Event {eEvent.Name} loaded successfully",LogType.Info); - }; - - loader.OnActionLoaded += (action) => - { - Application.CurrentApplication.Logger.Log($"Action {action.ActionName} loaded successfully", LogType.Info); - }; - - loader.OnSlashCommandLoaded += (slashCommand) => - { - Application.CurrentApplication.Logger.Log($"Slash Command {slashCommand.Name} loaded successfully", LogType.Info); - }; - - await loader.LoadPlugins(); - return true; - } - - -} diff --git a/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs b/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs deleted file mode 100644 index b0a6317..0000000 --- a/DiscordBot/Bot/Actions/Extra/SettingsConfigExtra.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using DiscordBotCore; - -namespace DiscordBot.Bot.Actions.Extra; - -internal static class SettingsConfigExtra -{ - - internal static void SetSettings(string key, params string[] value) - { - if (key is null) return; - - if (value is null) return; - - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey(key)) - return; - - Application.CurrentApplication.ApplicationEnvironmentVariables.Add(key, string.Join(' ', value)); - } - - internal static void RemoveSettings(string key) - { - if (key is null) return; - - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey(key)) - return; - - Application.CurrentApplication.ApplicationEnvironmentVariables.Remove(key); - } - - internal static void AddSettings(string key, params string[] value) - { - if (key is null) return; - - if (value is null) return; - - if (Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey(key)) - return; - - Application.CurrentApplication.ApplicationEnvironmentVariables.Add(key, string.Join(' ', value)); - // Config.Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile().Wait(); - } -} diff --git a/DiscordBot/Bot/Actions/Help.cs b/DiscordBot/Bot/Actions/Help.cs deleted file mode 100644 index 9f4a849..0000000 --- a/DiscordBot/Bot/Actions/Help.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DiscordBot.Utilities; - -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; -using Spectre.Console; - -namespace DiscordBot.Bot.Actions; - -public class Help: ICommandAction -{ - public string ActionName => "help"; - - public string Description => "Shows the list of commands and their usage"; - - public string Usage => "help "; - - public IEnumerable ListOfOptions => [ - new InternalActionOption("command", "The command to get help for") - ]; - - public InternalActionRunType RunType => InternalActionRunType.OnCall; - - public bool RequireOtherThread => false; - - public async Task Execute(string[] args) - { - TableData tableData = new TableData(); - - if (args == null || args.Length == 0) - { - - AnsiConsole.MarkupLine("[bold][green]Please make this window full screen to check all the commands.[/][/]"); - - tableData.Columns = ["Command", "Usage", "Description", "Options"]; - - foreach (var a in Application.CurrentApplication.InternalActionManager.GetActions()) - { - Markup actionName = new Markup($"[bold]{a.ActionName}[/]"); - Markup usage = new Markup($"[italic]{a.Usage}[/]"); - Markup description = new Markup($"[dim]{a.Description}[/]"); - - if (a.ListOfOptions.Any()) - { - tableData.AddRow([actionName, usage, description, CreateTableWithSubOptions(a.ListOfOptions)]); - } - else - { - tableData.AddRow([actionName, usage, description]); - } - - } - - // render the table - tableData.HasRoundBorders = true; - tableData.DisplayLinesBetweenRows = true; - tableData.PrintTable(); - - - return; - } - - if (!Application.CurrentApplication.InternalActionManager.Exists(args[0])) - { - Console.WriteLine("Command not found"); - return; - } - - var action = Application.CurrentApplication.InternalActionManager.GetAction(args[0]); - tableData.Columns = ["Command", "Usage", "Description"]; - tableData.AddRow([action.ActionName, action.Usage, action.Description]); - - - tableData.PrintTable(); - } - - private Table CreateTableWithSubOptions(IEnumerable options) - { - var tableData = new TableData(); - tableData.Columns = ["Option", "Description", "SubOptions"]; - - foreach (var option in options) - { - - Markup optionName = new Markup($"{option.OptionName}"); - Markup description = new Markup($"{option.OptionDescription}"); - - if(option.SubOptions.Any()) - { - tableData.AddRow([optionName, description, CreateTableWithSubOptions(option.SubOptions)]); - - }else { - tableData.AddRow([optionName, description]); - } - - } - - return tableData.AsTable(); - } -} diff --git a/DiscordBot/Bot/Actions/Plugin.cs b/DiscordBot/Bot/Actions/Plugin.cs deleted file mode 100644 index 3b915aa..0000000 --- a/DiscordBot/Bot/Actions/Plugin.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using DiscordBot.Bot.Actions.Extra; -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Actions; - -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 "; - - 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("name", "The name of the plugin to install") - ]), - new InternalActionOption("refresh", "Refreshes the plugin list"), - new InternalActionOption("uninstall", "Uninstalls a plugin"), - new InternalActionOption("branch", "Sets a plugin option", [ - new InternalActionOption("set", "Sets the branch"), - new InternalActionOption("get", "Gets the branch") - ]), - new InternalActionOption("enable", "Enables a plugin", [ - new InternalActionOption("name", "The name of the plugin to enable") - ]), - new InternalActionOption("disable", "Disables a plugin", [ - new InternalActionOption("name", "The name of the plugin to disable") - ]) - }; - - public InternalActionRunType RunType => InternalActionRunType.OnCall; - - public bool RequireOtherThread => false; - - public async Task Execute(string[] args) - { - if (args is null || args.Length == 0) - { - await Application.CurrentApplication.InternalActionManager.Execute("help", ActionName); - return; - } - - switch (args[0]) - { - case "enable": - { - if (args.Length < 2) - { - Console.WriteLine("Usage : plugin enable "); - return; - } - - string pluginName = string.Join(' ', args, 1, args.Length - 1); - await PluginMethods.EnablePlugin(pluginName); - - break; - } - case "disable": - { - if (args.Length < 2) - { - Console.WriteLine("Usage : plugin disable "); - return; - } - - string pluginName = string.Join(' ', args, 1, args.Length - 1); - await PluginMethods.DisablePlugin(pluginName); - - break; - } - case "refresh": - await PluginMethods.RefreshPlugins(true); - break; - - case "uninstall": - { - string plugName = string.Join(' ', args, 1, args.Length - 1); - bool result = await Application.CurrentApplication.PluginManager.MarkPluginToUninstall(plugName); - if (result) - Console.WriteLine($"Marked to uninstall plugin {plugName}. Please restart the bot"); - break; - } - case "list": - await PluginMethods.List(); - break; - case "load": - if (pluginsLoaded) - { - Application.CurrentApplication.Logger.Log("Plugins already loaded", this, LogType.Warning); - break; - } - - pluginsLoaded = await PluginMethods.LoadPlugins(args); - break; - - case "install": - { - var pluginName = string.Join(' ', args, 1, args.Length - 1); - if (string.IsNullOrEmpty(pluginName) || pluginName.Length < 2) - { - Console.WriteLine("Please specify a plugin name"); - Console.Write("Plugin name : "); - pluginName = Console.ReadLine(); - if (string.IsNullOrEmpty(pluginName) || pluginName.Length < 2) - { - Console.WriteLine("Invalid plugin name"); - break; - } - } - - await PluginMethods.DownloadPluginWithParallelDownloads(pluginName); - - break; - } - - } - } -} diff --git a/DiscordBot/Bot/Actions/SettingsConfig.cs b/DiscordBot/Bot/Actions/SettingsConfig.cs deleted file mode 100644 index 71e4a81..0000000 --- a/DiscordBot/Bot/Actions/SettingsConfig.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DiscordBot.Bot.Actions.Extra; -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Actions; - -public class SettingsConfig: ICommandAction -{ - public string ActionName => "config"; - public string Description => "Change the settings of the bot"; - 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.OnCall; - - public bool RequireOtherThread => false; - - public Task Execute(string[] args) - { - if (args is null) - { - PrintAllSettings(); - - return Task.CompletedTask; - } - - switch (args[0]) - { - case "-s": - case "set": - if (args.Length < 3) - return Task.CompletedTask; - SettingsConfigExtra.SetSettings(args[1], args[2..]); - break; - - case "-r": - case "remove": - if (args.Length < 2) - return Task.CompletedTask; - SettingsConfigExtra.RemoveSettings(args[1]); - break; - - case "-a": - case "add": - if (args.Length < 3) - return Task.CompletedTask; - SettingsConfigExtra.AddSettings(args[1], args[2..]); - break; - - case "-h": - case "help": - Console.WriteLine("Options:"); - Console.WriteLine("-s : Set a setting"); - Console.WriteLine("-r : Remove a setting"); - 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; - } - private void PrintList(IList list, int indentLevel) - { - bool isListOfDictionaries = list.All(item => item is IDictionary); - - if (isListOfDictionaries) - { - foreach (var item in list) - { - if (item is IDictionary dict) - { - PrintDictionary(dict, indentLevel + 1); - } - } - } - else - { - PrintIndent(indentLevel); - Console.WriteLine(string.Join(",", list)); - } - } - - private void PrintDictionary(IDictionary dictionary, int indentLevel) - { - foreach (var kvp in dictionary) - { - PrintIndent(indentLevel); - Console.Write(kvp.Key + ": "); - - var value = kvp.Value; - if (value is IDictionary dict) - { - Console.WriteLine(); - PrintDictionary(dict, indentLevel + 1); - } - else if (value is IList list) - { - if (list.All(item => item is IDictionary)) - { - Console.WriteLine(); - PrintList(list, indentLevel + 1); - } - else - { - PrintList(list, indentLevel); - } - } - else - { - Console.WriteLine(value); - } - } - } - - private void PrintIndent(int indentLevel) - { - for (int i = 0; i < indentLevel; i++) - { - Console.Write(" "); // Two spaces for each indentation level - } - } - - private void PrintAllSettings() - { - var settings = Application.CurrentApplication.ApplicationEnvironmentVariables; - foreach (var setting in settings) - { - Console.WriteLine("Setting: " + setting.Key); - if (setting.Value is IDictionary dict) - { - PrintDictionary(dict, 1); - } - else if (setting.Value is IList list) - { - PrintList(list, 1); - } - else - { - Console.WriteLine(setting.Value); - } - } - } -} diff --git a/DiscordBot/Bot/Commands/NormalCommands/Help.cs b/DiscordBot/Bot/Commands/NormalCommands/Help.cs deleted file mode 100644 index 0b9e31f..0000000 --- a/DiscordBot/Bot/Commands/NormalCommands/Help.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using Discord; -using DiscordBotCore; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Loaders; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Commands; - -/// -/// The help command -/// -internal class Help: IDbCommand -{ - /// - /// 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; - - string prefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get("prefix"); - - embedBuilder.AddField("Usage", 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/DiscordBot/Bot/Commands/SlashCommands/Help.cs b/DiscordBot/Bot/Commands/SlashCommands/Help.cs deleted file mode 100644 index 724ec29..0000000 --- a/DiscordBot/Bot/Commands/SlashCommands/Help.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Discord; -using Discord.WebSocket; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Loaders; -using DiscordBotCore.Others; - -namespace DiscordBot.Bot.Commands.SlashCommands; - -public class Help: IDbSlashCommand -{ - public string Name => "help"; - public string Description => "This command allows you to check all loaded commands"; - public bool CanUseDm => true; - - public bool HasInteraction => false; - - public List Options => - new() - { - new SlashCommandOptionBuilder() - .WithName("command") - .WithDescription("The command you want to get help for") - .WithRequired(false) - .WithType(ApplicationCommandOptionType.String) - }; - - public async void ExecuteServer(SocketSlashCommand context) - { - EmbedBuilder embedBuilder = new(); - - embedBuilder.WithTitle("Help Command"); - - var random = new Random(); - Color c = new Color(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)); - - embedBuilder.WithColor(c); - var slashCommands = PluginLoader.SlashCommands; - var options = context.Data.Options; - - //Console.WriteLine("Options: " + options.Count); - if (options is null || options.Count == 0) - foreach (var slashCommand in slashCommands) - embedBuilder.AddField(slashCommand.Name, slashCommand.Description); - - if (options.Count > 0) - { - 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("DM Usable:", slashCommand.CanUseDm, true) - .WithDescription(slashCommand.Description); - } - - await context.RespondAsync(embed: embedBuilder.Build()); - } -} diff --git a/DiscordBot/DiscordBot.csproj b/DiscordBot/DiscordBot.csproj deleted file mode 100644 index f80cc67..0000000 --- a/DiscordBot/DiscordBot.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - Exe - net8.0 - enable - - - False - True - 1.0.4.0 - False - 1.0.4.0 - false - Linux - - - full - AnyCPU - - - full - - - - - - - - - - - - - - - - - - - - - - - - - - - - .dockerignore - - - \ No newline at end of file diff --git a/DiscordBot/Dockerfile b/DiscordBot/Dockerfile deleted file mode 100644 index 179b370..0000000 --- a/DiscordBot/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER $APP_UID -WORKDIR /app -EXPOSE 5055 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["DiscordBot/DiscordBot.csproj", "DiscordBot/"] -COPY ["DiscordBotCore/DiscordBotCore.csproj", "DiscordBotCore/"] -RUN dotnet restore "DiscordBot/DiscordBot.csproj" -COPY . . -WORKDIR "/src/DiscordBot" -RUN dotnet build "DiscordBot.csproj" -c $BUILD_CONFIGURATION -o /app/build - -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "DiscordBot.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -EXPOSE 5055 -ENTRYPOINT ["dotnet", "DiscordBot.dll"] diff --git a/DiscordBot/Entry.cs b/DiscordBot/Entry.cs deleted file mode 100644 index f3da4aa..0000000 --- a/DiscordBot/Entry.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace DiscordBot; - -public static class Entry -{ - /// - /// Some startup actions that can are executed when the console first starts. This actions are invoked externally at application launch - /// - private static readonly List StartupActions = [ - new StartupAction("--clean-up", () => { - foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories)) - { - File.Delete(plugin); - } - - File.Delete("./Data/Resources/plugins.json"); - Directory.Delete("./Libraries/", true); - }), - - new StartupAction("--update-cleanup", () => { - List files = new List(); - files.AddRange(Directory.GetFiles("./")); - - foreach (var file in files) - { - if (file.EndsWith(".bak")) - File.Delete(file); - } - - Directory.Delete("temp"); - }) - ]; - - private static readonly string logo = -@" - - _____ _ _ _____ _ _ ____ _ - / ____| | | | | | __ \(_) | | | _ \ | | - | (___ ___| |_| |__ | | | |_ ___ ___ ___ _ __ __| | | |_) | ___ | |_ - \___ \ / _ \ __| '_ \ | | | | / __|/ __/ _ \| '__/ _` | | _ < / _ \| __| - ____) | __/ |_| | | | | |__| | \__ \ (_| (_) | | | (_| | | |_) | (_) | |_ - |_____/ \___|\__|_| |_| |_____/|_|___/\___\___/|_| \__,_| |____/ \___/ \__| - (Console Application) - -"; - public static void Main(string[] args) - { - if (args.Length > 0) - { - - StartupActions.FirstOrDefault(action => action.Command == args[0], null)?.RunAction(args[1..]); - } - - Console.Clear(); - - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine(logo); - Console.ResetColor(); - - - var currentDomain = AppDomain.CurrentDomain; - currentDomain.AssemblyResolve += LoadFromSameFolder; - - static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) - { - string requestingAssembly = args.RequestingAssembly?.GetName().Name; - var folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, $"Libraries/{requestingAssembly}"); - var assemblyName = new AssemblyName(args.Name).Name + ".dll"; - var assemblyPath = Path.Combine(folderPath, assemblyName); - - Console.WriteLine("Requesting Assembly: " + requestingAssembly); - Console.WriteLine("Requested Assembly File: " + assemblyName); - - if (File.Exists(assemblyPath)) - { - var fileAssembly = Assembly.LoadFrom(assemblyPath); - Console.WriteLine("Loaded Assembly: " + fileAssembly.FullName); - return fileAssembly; - } - - Console.WriteLine("Failed to load assembly: " + assemblyPath + ". File does not exist"); - return null; - } - - Program.Startup(args).Wait(); - - } - - -} diff --git a/DiscordBot/IStartupAction.cs b/DiscordBot/IStartupAction.cs deleted file mode 100644 index 637a5a1..0000000 --- a/DiscordBot/IStartupAction.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace DiscordBot -{ - internal interface IStartupAction - { - string Command { get; init; } - Action RunAction { get; init; } - } -} \ No newline at end of file diff --git a/DiscordBot/Installer.cs b/DiscordBot/Installer.cs deleted file mode 100644 index 67effe9..0000000 --- a/DiscordBot/Installer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using DiscordBotCore; -using DiscordBotCore.Others; -using Spectre.Console; - -namespace DiscordBot; - -public static class Installer -{ - private static string AskForConfig(string key, string message) - { - var value = AnsiConsole.Ask($"[green]{message}[/]"); - - if (!string.IsNullOrWhiteSpace(value)) - { - return value; - } - - AnsiConsole.MarkupLine($"Invalid {key} !"); - Environment.Exit(-20); - - return value; - - } - - public static async Task GenerateStartupConfig() - { - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("token")) - { - var response = AskForConfig("token", "Token:"); - Application.CurrentApplication.ApplicationEnvironmentVariables.Add("token", response); - } - - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("prefix")) - { - var response = AskForConfig("prefix", "Prefix:"); - Application.CurrentApplication.ApplicationEnvironmentVariables.Add("prefix", response); - } - - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("ServerID")) - { - var response = AskForConfig("ServerID", "Please enter the server Ids where the bot will be used (separated by ;):"); - List serverIds = new List(); - foreach (var id in response.Split(';')) - { - if(!ulong.TryParse(id, out ulong sID)) - { - Application.CurrentApplication.Logger.Log($"Invalid server ID {id}", LogType.Warning); - } - - serverIds.Add(sID); - } - - if(!serverIds.Any()) - { - Application.CurrentApplication.Logger.Log($"No valid server id provided", LogType.Critical); - Environment.Exit(-20); - } - - Application.CurrentApplication.ApplicationEnvironmentVariables.Add("ServerID", serverIds); - } - - await Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile(); - - Application.CurrentApplication.Logger.Log("Config Saved", typeof(Installer)); - } -} diff --git a/DiscordBot/Program.cs b/DiscordBot/Program.cs deleted file mode 100644 index 0644398..0000000 --- a/DiscordBot/Program.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -using DiscordBot.Bot.Actions.Extra; -using DiscordBotCore; -using DiscordBotCore.Bot; -using DiscordBotCore.Others; - -using Spectre.Console; - -namespace DiscordBot; - -public class Program -{ - /// - /// The main entry point for the application. - /// - public static async Task Startup(string[] args) - { - await LoadComponents(args); - await PrepareConsole(); - await PluginMethods.RefreshPlugins(false); - - await ConsoleInputHandler(); - } - - /// - /// The main loop for the discord bot - /// - private static async Task ConsoleInputHandler() - { - while (true) - { - var cmd = Console.ReadLine(); - var args = cmd.Split(' '); - var command = args[0]; - args = args.Skip(1).ToArray(); - if (args.Length == 0) - args = null; - - await Application.CurrentApplication.InternalActionManager.Execute(command, args); - } - } - - /// - /// Start the bot without user interface - /// - /// Returns the bootloader for the Discord Bot - private static async Task PrepareConsole() - { - AnsiConsole.MarkupLine($"[yellow]Running on version: {Assembly.GetExecutingAssembly().GetName().Version}[/]"); - AnsiConsole.MarkupLine("[yellow]Git SethBot: https://github.com/andreitdr/SethDiscordBot [/]"); - - 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]===== Seth Discord Bot =====[/]"); - - try - { - var token = Application.CurrentApplication.ApplicationEnvironmentVariables.Get("token"); - var prefix = Application.CurrentApplication.ApplicationEnvironmentVariables.Get("prefix"); - - var discordApp = new DiscordBotApplication(token, prefix); - await discordApp.StartAsync(); - } - catch (Exception ex) - { - Application.CurrentApplication.Logger.Log(ex.ToString(), typeof(Program), LogType.Critical); - } - } - - /// - /// Load the bot components. - /// - /// The startup arguments - private static async Task LoadComponents(string[] args) - { - await Application.CreateApplication(); - - void LogMessageFunction(string message, LogType logType) - { - string messageAsString = message; - switch (logType) - { - case LogType.Info: - messageAsString = $"[green]{messageAsString} [/]"; - break; - case LogType.Warning: - messageAsString = $"[yellow]{messageAsString} [/]"; - break; - case LogType.Error: - messageAsString = $"[red]{messageAsString} [/]"; - break; - case LogType.Critical: - messageAsString = $"[red] [bold]{messageAsString} [/][/]"; - break; - - } - - AnsiConsole.MarkupLine(messageAsString); - } - - Application.CurrentApplication.Logger.SetOutFunction(LogMessageFunction); - - - if (!Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("ServerID") || - !Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("token") || - !Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("prefix")) - { - await Installer.GenerateStartupConfig(); - } - - if (args.Length > 0) - { - if(args.Contains("--http-api")) - Application.InitializeThreadedApi(); - if(args.Contains("--socket-api")) - Application.InitializeThreadedSockets(); - } - - - } - -} diff --git a/DiscordBot/StartupAction.cs b/DiscordBot/StartupAction.cs deleted file mode 100644 index c67a56e..0000000 --- a/DiscordBot/StartupAction.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace DiscordBot -{ - internal class StartupAction : IStartupAction - { - public string Command { get; init; } - public Action RunAction { get; init; } - - public StartupAction(string command, Action runAction) - { - this.Command = command; - this.RunAction = runAction; - } - - public StartupAction(string command, Action runAction) - { - this.Command = command; - this.RunAction = (args) => runAction(); - } - } -} diff --git a/DiscordBot/Utilities/Console Utilities.cs b/DiscordBot/Utilities/Console Utilities.cs deleted file mode 100644 index c5ef1fc..0000000 --- a/DiscordBot/Utilities/Console Utilities.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using DiscordBotCore.Online; -using Spectre.Console; - -namespace DiscordBot.Utilities; - -internal static class ConsoleUtilities -{ - - public static async Task ExecuteWithProgressBar(Task function, string message) - { - T result = default; - await AnsiConsole.Progress() - .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 ExecuteTaskWithBuiltInProgress(Func, Task> method, T parameter, string taskMessage) - { - await AnsiConsole.Progress() - .AutoClear(false) // Do not remove the task list when done - .HideCompleted(false) // Hide tasks as they are completed - .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()) - .StartAsync( - async ctx => - { - var task = ctx.AddTask(taskMessage); - IProgress progress = new Progress(x => task.Value = x); - await method(parameter, progress); - task.Value = 100; - } - ); - - } - - public static async Task ExecuteTaskWithBuiltInProgress(Func, Task> method, string taskMessage) - { - await AnsiConsole.Progress() - .AutoClear(false) // Do not remove the task list when done - .HideCompleted(false) // Hide tasks as they are completed - .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()) - .StartAsync( - async ctx => - { - var task = ctx.AddTask(taskMessage); - IProgress progress = new Progress(x => task.Value = x); - await method(progress); - task.Value = 100; - } - ); - - } - - public static async Task ExecuteParallelDownload(Func, Task> method, HttpClient client, - List> parameters, string taskMessage) - { - await AnsiConsole.Progress().AutoClear(false).HideCompleted(false) - .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn()) - .StartAsync(async ctx => - { - var tasks = new List(); - foreach (var (location, url) in parameters) - { - var task = ctx.AddTask(taskMessage + " " + url); - IProgress progress = new Progress(x => task.Value = x * 100); - tasks.Add(method(client, url, location, progress)); - } - - await Task.WhenAll(tasks); - }); - } -} \ No newline at end of file diff --git a/DiscordBot/Utilities/TableData.cs b/DiscordBot/Utilities/TableData.cs deleted file mode 100644 index a41136f..0000000 --- a/DiscordBot/Utilities/TableData.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using DiscordBotCore.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 Table AsTable() - { - - 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( - (string data) => new Markup(data), - (IRenderable data) => data - ))); - } - - table.Alignment(Justify.Center); - - return table; - } - - public void PrintTable() - { - if (IsEmpty) return; - AnsiConsole.Write(this.AsTable()); - } - } -} diff --git a/DiscordBot/builder.bat b/DiscordBot/builder.bat deleted file mode 100644 index f73d6b4..0000000 --- a/DiscordBot/builder.bat +++ /dev/null @@ -1,35 +0,0 @@ -@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/DiscordBot/builder.sh b/DiscordBot/builder.sh deleted file mode 100755 index 0e53c37..0000000 --- a/DiscordBot/builder.sh +++ /dev/null @@ -1,36 +0,0 @@ -# All files in this directory will be copied to the root of the container - -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 - -#One file per platform -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/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs b/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs index 67ef5a1..b02f3bb 100644 --- a/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs +++ b/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs @@ -1,15 +1,27 @@ using Discord.WebSocket; -using DiscordBotCore.PluginCore; using DiscordBotCore.PluginCore.Interfaces; namespace DiscordBotCore.PluginManagement.Loading; public interface IPluginLoader { - List Commands { get; } - List Events { get; } - List SlashCommands { get; } - Task LoadPlugins(); + public IReadOnlyList Commands { get; } + public IReadOnlyList Events { get; } + public IReadOnlyList SlashCommands { get; } - void SetClient(DiscordSocketClient client); + /// + /// Sets the Discord client for the plugin loader. This is used to initialize the slash commands and events. + /// + /// The socket client that represents the running Discord Bot + public void SetDiscordClient(DiscordSocketClient discordSocketClient); + + /// + /// Loads all the plugins that are installed. + /// + public Task LoadPlugins(); + + /// + /// Unload all plugins from the plugin manager. + /// + public void UnloadAllPlugins(); } \ No newline at end of file diff --git a/DiscordBotCore.PluginManagement.Loading/Loader.cs b/DiscordBotCore.PluginManagement.Loading/Loader.cs deleted file mode 100644 index 7189eb5..0000000 --- a/DiscordBotCore.PluginManagement.Loading/Loader.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Reflection; -using DiscordBotCore.PluginCore; -using DiscordBotCore.PluginCore.Interfaces; -using DiscordBotCore.PluginManagement.Loading.Exceptions; - -namespace DiscordBotCore.PluginManagement.Loading; - -internal class Loader -{ - internal delegate void FileLoadedHandler(string fileName, Exception exception); - internal delegate void PluginLoadedHandler(PluginLoaderResult result); - - internal event FileLoadedHandler? OnFileLoadedException; - internal event PluginLoadedHandler? OnPluginLoaded; - - private readonly IPluginManager _pluginManager; - - internal Loader(IPluginManager manager) - { - _pluginManager = manager; - } - - internal async Task Load() - { - var installedPlugins = await _pluginManager.GetInstalledPlugins(); - var files = installedPlugins.Where(plugin => plugin.IsEnabled).Select(plugin => plugin.FilePath).ToArray(); - - foreach (var file in files) - { - try - { - Assembly.LoadFrom(file); - } - catch - { - OnFileLoadedException?.Invoke(file, new Exception($"Failed to load plugin from file {file}")); - } - } - - await LoadEverythingOfType(); - await LoadEverythingOfType(); - await LoadEverythingOfType(); - } - - private Task LoadEverythingOfType() - { - var types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(s => s.GetTypes()) - .Where(p => typeof(T).IsAssignableFrom(p) && !p.IsInterface); - - foreach (var type in types) - { - try - { - var plugin = (T?)Activator.CreateInstance(type); - - if (plugin is null) - { - throw new Exception($"Failed to create instance of plugin with type {type.FullName} [{type.Assembly}]"); - } - - PluginLoaderResult result = plugin switch - { - IDbEvent @event => PluginLoaderResult.FromIDbEvent(@event), - IDbCommand command => PluginLoaderResult.FromIDbCommand(command), - IDbSlashCommand command => PluginLoaderResult.FromIDbSlashCommand(command), - _ => PluginLoaderResult.FromException(new PluginNotFoundException($"Unknown plugin type {plugin.GetType().FullName}")) - }; - - OnPluginLoaded?.Invoke(result); - } - catch (Exception ex) - { - OnPluginLoaded?.Invoke(PluginLoaderResult.FromException(ex)); - } - } - - return Task.CompletedTask; - } - -} diff --git a/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs b/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs index f1982f1..542b730 100644 --- a/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs +++ b/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs @@ -1,27 +1,29 @@ -using System.Net.Mime; +using System.Collections.ObjectModel; +using System.Reflection; using Discord; using Discord.WebSocket; using DiscordBotCore.Configuration; using DiscordBotCore.Logging; -using DiscordBotCore.PluginCore; -using DiscordBotCore.PluginCore.Helpers; using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent; using DiscordBotCore.PluginCore.Interfaces; -using DiscordBotCore.Utilities; +using DiscordBotCore.PluginManagement.Loading.Exceptions; namespace DiscordBotCore.PluginManagement.Loading; -public sealed class PluginLoader : IPluginLoader +public class PluginLoader : IPluginLoader { private readonly IPluginManager _PluginManager; private readonly ILogger _Logger; private readonly IConfiguration _Configuration; - - public List Commands { get; private set; } = new List(); - public List Events { get; private set; } = new List(); - public List SlashCommands { get; private set; } = new List(); - - private DiscordSocketClient? _discordClient; + + private DiscordSocketClient? _DiscordClient; + private PluginLoaderContext? PluginLoaderContext; + + private readonly List _Commands = new List(); + private readonly List _Events = new List(); + private readonly List _SlashCommands = new List(); + + private bool _IsFirstLoad = true; public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration) { @@ -29,49 +31,151 @@ public sealed class PluginLoader : IPluginLoader _Logger = logger; _Configuration = configuration; } + + public IReadOnlyList Commands => _Commands; + public IReadOnlyList Events => _Events; + public IReadOnlyList SlashCommands => _SlashCommands; - public async Task LoadPlugins() + public void SetDiscordClient(DiscordSocketClient discordSocketClient) { - Commands.Clear(); - Events.Clear(); - SlashCommands.Clear(); - - _Logger.Log("Loading plugins...", this); - - var loader = new Loader(_PluginManager); - - loader.OnFileLoadedException += FileLoadedException; - loader.OnPluginLoaded += OnPluginLoaded; - - await loader.Load(); - } - - public void SetClient(DiscordSocketClient client) - { - if (_discordClient is not null) + if (_DiscordClient is not null) { _Logger.Log("A client is already set. Please set the client only once.", this, LogType.Error); return; } - if (client.LoginState != LoginState.LoggedIn) + if (discordSocketClient.LoginState != LoginState.LoggedIn) { - _Logger.Log("Client is not logged in. Retry after the client is logged in", this, LogType.Error); + _Logger.Log("The client must be logged in before setting it.", this, LogType.Error); + return; + } + + _DiscordClient = discordSocketClient; + } + + public async Task LoadPlugins() + { + UnloadAllPlugins(); + + _Events.Clear(); + _Commands.Clear(); + _SlashCommands.Clear(); + + await LoadPluginFiles(); + + LoadEverythingOfType(); + LoadEverythingOfType(); + LoadEverythingOfType(); + + _Logger.Log("Loaded plugins", this); + } + + public void UnloadAllPlugins() + { + if (_IsFirstLoad) + { + // Allow unloading only after the first load + _IsFirstLoad = false; return; } - _Logger.Log("Client is set to the plugin loader", this); - _discordClient = client; + if (PluginLoaderContext is null) + { + _Logger.Log("The plugins are not loaded. Please load the plugins before unloading them.", this, LogType.Error); + return; + } + + PluginLoaderContext.Unload(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + PluginLoaderContext = null; } - private void FileLoadedException(string fileName, Exception exception) + private async Task LoadPluginFiles() { - _Logger.LogException(exception, this); + if (PluginLoaderContext is not null) + { + _Logger.Log("The plugins are already loaded", this, LogType.Error); + return; + } + + var installedPlugins = await _PluginManager.GetInstalledPlugins(); + + if (installedPlugins.Count == 0) + { + _Logger.Log("No plugin files found. Please check the plugin files.", this, LogType.Error); + return; + } + + var files = installedPlugins.Where(plugin => plugin.IsEnabled).Select(plugin => plugin.FilePath); + + PluginLoaderContext = new PluginLoaderContext("PluginLoader"); + + foreach (var file in files) + { + string fullFilePath = Path.GetFullPath(file); + if (string.IsNullOrEmpty(fullFilePath)) + { + _Logger.Log("The file path is empty. Please check the plugin file path.", PluginLoaderContext, LogType.Error); + continue; + } + + if (!File.Exists(fullFilePath)) + { + _Logger.Log("The file does not exist. Please check the plugin file path.", PluginLoaderContext, LogType.Error); + continue; + } + + try + { + PluginLoaderContext.LoadFromAssemblyPath(fullFilePath); + } + catch (Exception ex) + { + _Logger.LogException(ex, this); + } + } + + _Logger.Log($"Loaded {PluginLoaderContext.Assemblies.Count()} assemblies", this); } + private void LoadEverythingOfType() + { + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => typeof(T).IsAssignableFrom(p) && !p.IsInterface); + + foreach (var type in types) + { + T? plugin = (T?)Activator.CreateInstance(type); + if (plugin is null) + { + _Logger.Log($"Failed to create instance of plugin with type {type.FullName} [{type.Assembly}]", this, LogType.Error); + continue; + } + + switch (plugin) + { + case IDbEvent dbEvent: + InitializeEvent(dbEvent); + break; + case IDbCommand dbCommand: + InitializeDbCommand(dbCommand); + break; + case IDbSlashCommand dbSlashCommand: + InitializeSlashCommand(dbSlashCommand); + break; + default: + throw new PluginNotFoundException($"Unknown plugin type {plugin.GetType().FullName}"); + } + } + } + private void InitializeDbCommand(IDbCommand command) { - Commands.Add(command); + _Commands.Add(command); _Logger.Log("Command loaded: " + command.Command, this); } @@ -82,38 +186,31 @@ public sealed class PluginLoader : IPluginLoader return; } - Events.Add(eEvent); + _Events.Add(eEvent); _Logger.Log("Event loaded: " + eEvent, this); } private async void InitializeSlashCommand(IDbSlashCommand slashCommand) { - Result result = await TryStartSlashCommand(slashCommand); - result.Match( - () => - { - if (slashCommand.HasInteraction) - _discordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction); - SlashCommands.Add(slashCommand); - _Logger.Log("Slash command loaded: " + slashCommand.Name, this); - }, - HandleError - ); - } + bool result = await TryStartSlashCommand(slashCommand); - private void HandleError(Exception exception) - { - _Logger.LogException(exception, this); - } + if (!result) + { + return; + } - private void OnPluginLoaded(PluginLoaderResult result) - { - result.Match( - InitializeDbCommand, - InitializeEvent, - InitializeSlashCommand, - HandleError - ); + if (_DiscordClient is null) + { + return; + } + + if (slashCommand.HasInteraction) + { + _DiscordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction); + } + + _SlashCommands.Add(slashCommand); + _Logger.Log("Slash command loaded: " + slashCommand.Name, this); } private bool TryStartEvent(IDbEvent dbEvent) @@ -125,7 +222,7 @@ public sealed class PluginLoader : IPluginLoader return false; } - if (_discordClient is null) + if (_DiscordClient is null) { _Logger.Log("Discord client is not set. Please set the discord client before starting events.", this, LogType.Error); return false; @@ -150,7 +247,7 @@ public sealed class PluginLoader : IPluginLoader IDbEventExecutingArgument args = new DbEventExecutingArgument( _Logger, - _discordClient, + _DiscordClient, botPrefix, new DirectoryInfo(eventConfigDirectory)); @@ -158,58 +255,60 @@ public sealed class PluginLoader : IPluginLoader return true; } - private async Task TryStartSlashCommand(IDbSlashCommand? dbSlashCommand) + private async Task TryStartSlashCommand(IDbSlashCommand? dbSlashCommand) { - try + if (dbSlashCommand is null) { - if (dbSlashCommand is null) - { - return Result.Failure(new Exception("dbSlashCommand is null")); - } + _Logger.Log("The loaded slash command was null. Please check the plugin.", this, LogType.Error); + return false; + } - if (_discordClient.Guilds.Count == 0) - { - return Result.Failure(new Exception("No guilds found")); - } + if (_DiscordClient is null) + { + _Logger.Log("The client is not set. Please set the client before starting slash commands.", this, LogType.Error); + return false; + } - var builder = new SlashCommandBuilder(); - builder.WithName(dbSlashCommand.Name); - builder.WithDescription(dbSlashCommand.Description); - builder.Options = dbSlashCommand.Options; + if (_DiscordClient.Guilds.Count == 0) + { + _Logger.Log("The client is not connected to any guilds. Please check the client.", this, LogType.Error); + return false; + } - if (dbSlashCommand.CanUseDm) - builder.WithContextTypes(InteractionContextType.BotDm, InteractionContextType.Guild); - else - builder.WithContextTypes(InteractionContextType.Guild); + var builder = new SlashCommandBuilder(); + builder.WithName(dbSlashCommand.Name); + builder.WithDescription(dbSlashCommand.Description); + builder.Options = dbSlashCommand.Options; - List serverIds = _Configuration.GetList("ServerIds", new List()); + if (dbSlashCommand.CanUseDm) + builder.WithContextTypes(InteractionContextType.BotDm, InteractionContextType.Guild); + else + builder.WithContextTypes(InteractionContextType.Guild); + + List serverIds = _Configuration.GetList("ServerIds", new List()); - foreach(ulong guildId in serverIds) - { - bool result = await EnableSlashCommandPerGuild(guildId, builder); + foreach(ulong guildId in serverIds) + { + bool result = await EnableSlashCommandPerGuild(guildId, builder); - if (!result) - { - return Result.Failure($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}"); - } + if (!result) + { + _Logger.Log($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}", this, LogType.Error); + return false; } + } - await _discordClient.CreateGlobalApplicationCommandAsync(builder.Build()); + await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build()); - return Result.Success(); - } - catch (Exception e) - { - return Result.Failure("Error starting slash command"); - } + return true; } private async Task EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder) { - SocketGuild? guild = _discordClient.GetGuild(guildId); + SocketGuild? guild = _DiscordClient?.GetGuild(guildId); if (guild is null) { - _Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error); + _Logger.Log("Failed to get guild with ID " + guildId, this, LogType.Error); return false; } @@ -217,4 +316,4 @@ public sealed class PluginLoader : IPluginLoader return true; } -} +} \ No newline at end of file diff --git a/DiscordBotCore.PluginManagement.Loading/PluginLoaderContext.cs b/DiscordBotCore.PluginManagement.Loading/PluginLoaderContext.cs new file mode 100644 index 0000000..dab6ea2 --- /dev/null +++ b/DiscordBotCore.PluginManagement.Loading/PluginLoaderContext.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace DiscordBotCore.PluginManagement.Loading; + +public class PluginLoaderContext : AssemblyLoadContext +{ + public PluginLoaderContext(string name) : base(name: name, isCollectible: true) {} + + protected override Assembly? Load(AssemblyName assemblyName) + { + + return base.Load(assemblyName); + } +} \ No newline at end of file diff --git a/DiscordBotCore.PluginManagement.Loading/PluginLoaderResult.cs b/DiscordBotCore.PluginManagement.Loading/PluginLoaderResult.cs deleted file mode 100644 index 00fe7c5..0000000 --- a/DiscordBotCore.PluginManagement.Loading/PluginLoaderResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -using DiscordBotCore.PluginCore; -using DiscordBotCore.PluginCore.Interfaces; -using DiscordBotCore.Utilities; - -namespace DiscordBotCore.PluginManagement.Loading; - - -public class PluginLoaderResult -{ - private Option3 _Result; - - public static PluginLoaderResult FromIDbCommand(IDbCommand command) => new PluginLoaderResult(new Option3(command)); - - public static PluginLoaderResult FromIDbEvent(IDbEvent dbEvent) => new PluginLoaderResult(new Option3(dbEvent)); - - public static PluginLoaderResult FromIDbSlashCommand(IDbSlashCommand slashCommand) => new PluginLoaderResult(new Option3(slashCommand)); - - public static PluginLoaderResult FromException(Exception exception) => new PluginLoaderResult(new Option3(exception)); - private PluginLoaderResult(Option3 result) - { - _Result = result; - } - - public void Match(Action commandAction, Action eventAction, Action slashCommandAction, - Action exceptionAction) - { - _Result.Match(commandAction, eventAction, slashCommandAction, exceptionAction); - } - - public TResult Match(Func commandFunc, Func eventFunc, - Func slashCommandFunc, - Func exceptionFunc) - { - return _Result.Match(commandFunc, eventFunc, slashCommandFunc, exceptionFunc); - } - -} \ No newline at end of file diff --git a/DiscordBotCore/Bot/DiscordBotApplication.cs b/DiscordBotCore/Bot/DiscordBotApplication.cs index 6b259a7..6880d8d 100644 --- a/DiscordBotCore/Bot/DiscordBotApplication.cs +++ b/DiscordBotCore/Bot/DiscordBotApplication.cs @@ -18,7 +18,7 @@ public class DiscordBotApplication : IDiscordBotApplication private CommandService _Service; private readonly ILogger _Logger; private readonly IConfiguration _Configuration; - private readonly IPluginLoader _PluginLoader; + private readonly IPluginLoader _pluginLoader; public bool IsReady { get; private set; } @@ -27,11 +27,11 @@ public class DiscordBotApplication : IDiscordBotApplication /// /// The main Boot constructor /// - public DiscordBotApplication(ILogger logger, IConfiguration configuration, IPluginLoader pluginLoader) + public DiscordBotApplication(ILogger logger, IConfiguration configuration, IPluginLoader pluginLoaderOld) { this._Logger = logger; this._Configuration = configuration; - this._PluginLoader = pluginLoader; + this._pluginLoader = pluginLoaderOld; } public async Task StopAsync() @@ -84,7 +84,7 @@ public class DiscordBotApplication : IDiscordBotApplication await client.StartAsync(); - _CommandServiceHandler = new CommandHandler(_Logger, _PluginLoader, _Configuration, _Service, _Configuration.Get("prefix", _DefaultPrefix)); + _CommandServiceHandler = new CommandHandler(_Logger, _pluginLoader, _Configuration, _Service, _Configuration.Get("prefix", _DefaultPrefix)); await _CommandServiceHandler.InstallCommandsAsync(client); @@ -113,7 +113,7 @@ public class DiscordBotApplication : IDiscordBotApplication private Task LoggedIn() { _Logger.Log("Successfully Logged In", this); - _PluginLoader.SetClient(Client); + _pluginLoader.SetDiscordClient(Client); return Task.CompletedTask; } diff --git a/SethDiscordBot.sln b/SethDiscordBot.sln index 3d158aa..a899514 100644 --- a/SethDiscordBot.sln +++ b/SethDiscordBot.sln @@ -2,8 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32421.90 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBot", "DiscordBot\DiscordBot.csproj", "{087E64F4-1E1C-4899-8223-295356C9894A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotCore", "DiscordBotCore\DiscordBotCore.csproj", "{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{5CF9AD7B-6BF0-4035-835F-722F989C01E1}" @@ -45,10 +43,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {087E64F4-1E1C-4899-8223-295356C9894A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {087E64F4-1E1C-4899-8223-295356C9894A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {087E64F4-1E1C-4899-8223-295356C9894A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {087E64F4-1E1C-4899-8223-295356C9894A}.Release|Any CPU.Build.0 = Release|Any CPU {5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|Any CPU.ActiveCfg = Release|Any CPU