From d9d5c05313a3672a5e62382a5cfcbf94218b3b06 Mon Sep 17 00:00:00 2001 From: Andrei Tudor Date: Sat, 8 Jun 2024 19:17:15 +0300 Subject: [PATCH] Actions are now loaded together with all plugins. Called the LoadPlugins at startup --- DiscordBot/Bot/Actions/Extra/PluginMethods.cs | 23 ++++- DiscordBot/Bot/Actions/Help.cs | 16 ++-- DiscordBot/Bot/Commands/SlashCommands/Help.cs | 7 +- DiscordBot/Program.cs | 9 +- DiscordBotCore/Application.cs | 4 +- DiscordBotCore/Bot/{Boot.cs => App.cs} | 4 +- DiscordBotCore/Loaders/ActionsLoader.cs | 72 ---------------- DiscordBotCore/Loaders/Loader.cs | 17 +--- DiscordBotCore/Loaders/PluginLoader.cs | 21 ++++- .../Online/Helpers/OnlineFunctions.cs | 38 +++++++++ DiscordBotCore/Online/PluginManager.cs | 46 +++++----- .../Others/Actions/InternalActionsManager.cs | 41 +++++---- DiscordBotCore/Others/Enums.cs | 3 +- DiscordBotCore/Others/Functions.cs | 83 ------------------- 14 files changed, 148 insertions(+), 236 deletions(-) rename DiscordBotCore/Bot/{Boot.cs => App.cs} (98%) delete mode 100644 DiscordBotCore/Loaders/ActionsLoader.cs delete mode 100644 DiscordBotCore/Others/Functions.cs diff --git a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs b/DiscordBot/Bot/Actions/Extra/PluginMethods.cs index b3067e5..ab22e96 100644 --- a/DiscordBot/Bot/Actions/Extra/PluginMethods.cs +++ b/DiscordBot/Bot/Actions/Extra/PluginMethods.cs @@ -40,8 +40,9 @@ internal static class PluginMethods internal static async Task RefreshPlugins(bool quiet) { - await Application.CurrentApplication.InternalActionManager.Execute("plugin", "load", quiet ? "-q" : string.Empty); - await Application.CurrentApplication.InternalActionManager.Refresh(); + + await LoadPlugins(quiet ? ["-q"] : null); + await Application.CurrentApplication.InternalActionManager.Initialize(); } internal static async Task DownloadPlugin(PluginManager manager, string pluginName) @@ -156,7 +157,7 @@ internal static class PluginMethods internal static async Task LoadPlugins(string[] args) { var loader = new PluginLoader(Application.CurrentApplication.DiscordBotClient.Client); - if (args.Length == 2 && args[1] == "-q") + if (args != null && (args.Length == 2 && args[1] == "-q")) { await loader.LoadPlugins(); return true; @@ -211,6 +212,22 @@ internal static class PluginMethods Console.ForegroundColor = cc; }; + loader.OnActionLoaded += (data) => + { + if (data.IsSuccess) + { + Application.CurrentApplication.Logger.Log("Successfully loaded action : " + data.PluginName, LogType.INFO, "\t\t > {Message}"); + } + else + { + Application.CurrentApplication.Logger.Log("Failed to load action : " + data.PluginName + " because " + data.ErrorMessage, + typeof(PluginMethods), LogType.ERROR + ); + } + + Console.ForegroundColor = cc; + }; + await loader.LoadPlugins(); Console.ForegroundColor = cc; return true; diff --git a/DiscordBot/Bot/Actions/Help.cs b/DiscordBot/Bot/Actions/Help.cs index a0ab731..fcca846 100644 --- a/DiscordBot/Bot/Actions/Help.cs +++ b/DiscordBot/Bot/Actions/Help.cs @@ -38,15 +38,15 @@ public class Help: ICommandAction tableData.Columns = ["Command", "Usage", "Description", "Options"]; - foreach (var a in Application.CurrentApplication.InternalActionManager.Actions) + foreach (var a in Application.CurrentApplication.InternalActionManager.GetActions()) { - Markup actionName = new Markup($"[bold]{a.Key}[/]"); - Markup usage = new Markup($"[italic]{a.Value.Usage}[/]"); - Markup description = new Markup($"[dim]{a.Value.Description}[/]"); + Markup actionName = new Markup($"[bold]{a.ActionName}[/]"); + Markup usage = new Markup($"[italic]{a.Usage}[/]"); + Markup description = new Markup($"[dim]{a.Description}[/]"); - if (a.Value.ListOfOptions.Any()) + if (a.ListOfOptions.Any()) { - tableData.AddRow([actionName, usage, description, CreateTableWithSubOptions(a.Value.ListOfOptions)]); + tableData.AddRow([actionName, usage, description, CreateTableWithSubOptions(a.ListOfOptions)]); } else { @@ -64,13 +64,13 @@ public class Help: ICommandAction return; } - if (!Application.CurrentApplication.InternalActionManager.Actions.ContainsKey(args[0])) + if (!Application.CurrentApplication.InternalActionManager.Exists(args[0])) { Console.WriteLine("Command not found"); return; } - var action = Application.CurrentApplication.InternalActionManager.Actions[args[0]]; + var action = Application.CurrentApplication.InternalActionManager.GetAction(args[0]); tableData.Columns = ["Command", "Usage", "Description"]; tableData.AddRow([action.ActionName, action.Usage, action.Description]); diff --git a/DiscordBot/Bot/Commands/SlashCommands/Help.cs b/DiscordBot/Bot/Commands/SlashCommands/Help.cs index 0d741a3..56bf646 100644 --- a/DiscordBot/Bot/Commands/SlashCommands/Help.cs +++ b/DiscordBot/Bot/Commands/SlashCommands/Help.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Discord; @@ -31,7 +32,11 @@ public class Help: DBSlashCommand EmbedBuilder embedBuilder = new(); embedBuilder.WithTitle("Help Command"); - embedBuilder.WithColor(Functions.RandomColor); + + 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; diff --git a/DiscordBot/Program.cs b/DiscordBot/Program.cs index c05efab..da29545 100644 --- a/DiscordBot/Program.cs +++ b/DiscordBot/Program.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using DiscordBot.Bot.Actions.Extra; + using DiscordBotCore; using DiscordBotCore.Bot; using DiscordBotCore.Others; @@ -22,6 +24,8 @@ public class Program { await LoadComponents(args); await PrepareConsole(); + await PluginMethods.LoadPlugins(null); + await Application.CurrentApplication.InternalActionManager.Initialize(); await ConsoleInputHandler(); } @@ -30,7 +34,6 @@ public class Program /// private static async Task ConsoleInputHandler() { - await Application.CurrentApplication.InternalActionManager.Execute("plugin", "load"); while (true) { @@ -65,7 +68,7 @@ public class Program { var token = Application.CurrentApplication.ApplicationEnvironmentVariables["token"]; var prefix = Application.CurrentApplication.ApplicationEnvironmentVariables["prefix"]; - var discordbooter = new Boot(token, prefix); + var discordbooter = new App(token, prefix); await discordbooter.Awake(); } catch (Exception ex) @@ -118,6 +121,8 @@ public class Program !Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("token") || !Application.CurrentApplication.ApplicationEnvironmentVariables.ContainsKey("prefix")) await Installer.GenerateStartupConfig(); + + } } diff --git a/DiscordBotCore/Application.cs b/DiscordBotCore/Application.cs index fe484be..4647573 100644 --- a/DiscordBotCore/Application.cs +++ b/DiscordBotCore/Application.cs @@ -36,7 +36,7 @@ namespace DiscordBotCore public InternalActionManager InternalActionManager { get; private set; } public PluginManager PluginManager { get; private set; } public Logger Logger { get; private set; } - public Bot.Boot DiscordBotClient { get; internal set; } + public Bot.App DiscordBotClient { get; internal set; } public static async Task CreateApplication() { @@ -78,7 +78,7 @@ namespace DiscordBotCore await CurrentApplication.PluginManager.UninstallMarkedPlugins(); await CurrentApplication.PluginManager.CheckForUpdates(); - CurrentApplication.InternalActionManager = new InternalActionManager(CurrentApplication.ApplicationEnvironmentVariables["PluginFolder"], "dll"); + CurrentApplication.InternalActionManager = new InternalActionManager(); await CurrentApplication.InternalActionManager.Initialize(); } diff --git a/DiscordBotCore/Bot/Boot.cs b/DiscordBotCore/Bot/App.cs similarity index 98% rename from DiscordBotCore/Bot/Boot.cs rename to DiscordBotCore/Bot/App.cs index 093914b..5d25198 100644 --- a/DiscordBotCore/Bot/Boot.cs +++ b/DiscordBotCore/Bot/App.cs @@ -9,7 +9,7 @@ using DiscordBotCore.Others; namespace DiscordBotCore.Bot; -public class Boot +public class App { /// /// The bot prefix @@ -41,7 +41,7 @@ public class Boot /// /// The bot token /// The bot prefix - public Boot(string botToken, string botPrefix) + public App(string botToken, string botPrefix) { this.BotPrefix = botPrefix; this.BotToken = botToken; diff --git a/DiscordBotCore/Loaders/ActionsLoader.cs b/DiscordBotCore/Loaders/ActionsLoader.cs deleted file mode 100644 index 206d7f1..0000000 --- a/DiscordBotCore/Loaders/ActionsLoader.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using DiscordBotCore.Interfaces; -using DiscordBotCore.Others; - -namespace DiscordBotCore.Loaders; - -public class ActionsLoader -{ - public delegate void ActionLoaded(string name, string typeName, bool success, Exception? e = null); - - private readonly string _actionExtension; - - private readonly string _actionFolder; - - public ActionsLoader(string path, string extension) - { - _actionFolder = path; - _actionExtension = extension; - } - - public event ActionLoaded? ActionLoadedEvent; - - public async Task?> Load() - { - Directory.CreateDirectory(_actionFolder); - var files = Directory.GetFiles(_actionFolder, $"*.{_actionExtension}", SearchOption.AllDirectories); - - var actions = new List(); - - foreach (var file in files) - try - { - Assembly.LoadFrom(file); - } - catch (Exception e) - { - ActionLoadedEvent?.Invoke(file, "", false, e); - } - - var types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(s => s.GetTypes()) - .Where(p => typeof(ICommandAction).IsAssignableFrom(p) && !p.IsInterface); - - foreach (var type in types) - try - { - var action = (ICommandAction)Activator.CreateInstance(type); - if (action.ActionName == null) - { - ActionLoadedEvent?.Invoke(action.ActionName, type.Name, false); - continue; - } - - if (action.RunType != InternalActionRunType.ON_CALL) - action.ExecuteStartup(); - - ActionLoadedEvent?.Invoke(action.ActionName, type.Name, true); - actions.Add(action); - } - catch (Exception e) - { - ActionLoadedEvent?.Invoke(type.Name, type.Name, false, e); - } - - return actions; - } -} diff --git a/DiscordBotCore/Loaders/Loader.cs b/DiscordBotCore/Loaders/Loader.cs index 31bc820..f9be807 100644 --- a/DiscordBotCore/Loaders/Loader.cs +++ b/DiscordBotCore/Loaders/Loader.cs @@ -10,8 +10,6 @@ namespace DiscordBotCore.Loaders; internal class Loader { - private readonly string _SearchPath; - private readonly string _FileExtension; internal delegate void FileLoadedHandler(FileLoaderResult result); @@ -20,24 +18,11 @@ internal class Loader internal event FileLoadedHandler? OnFileLoadedException; internal event PluginLoadedHandler? OnPluginLoaded; - internal Loader(string searchPath, string fileExtension) - { - _SearchPath = searchPath; - _FileExtension = fileExtension; - } - internal async Task Load() { - if (!Directory.Exists(_SearchPath)) - { - Directory.CreateDirectory(_SearchPath); - return; - } - var installedPlugins = await Application.CurrentApplication.PluginManager.GetInstalledPlugins(); var files = installedPlugins.Select(plugin => plugin.FilePath).ToArray(); - //var files = Directory.GetFiles(_SearchPath, $"*.{_FileExtension}", SearchOption.TopDirectoryOnly); foreach (var file in files) { try @@ -53,6 +38,7 @@ internal class Loader await LoadEverythingOfType(); await LoadEverythingOfType(); await LoadEverythingOfType(); + await LoadEverythingOfType(); } private async Task LoadEverythingOfType() @@ -77,6 +63,7 @@ internal class Loader DBEvent => PluginType.EVENT, DBCommand => PluginType.COMMAND, DBSlashCommand => PluginType.SLASH_COMMAND, + ICommandAction => PluginType.ACTION, _ => PluginType.UNKNOWN }; diff --git a/DiscordBotCore/Loaders/PluginLoader.cs b/DiscordBotCore/Loaders/PluginLoader.cs index edabc77..99e1138 100644 --- a/DiscordBotCore/Loaders/PluginLoader.cs +++ b/DiscordBotCore/Loaders/PluginLoader.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Discord.WebSocket; using DiscordBotCore.Interfaces; @@ -17,13 +19,17 @@ public class PluginLoader public delegate void SlashCommandLoaded(PluginLoadResultData resultData); + public delegate void ActionLoaded(PluginLoadResultData resultData); + public CommandLoaded? OnCommandLoaded; public EventLoaded? OnEventLoaded; public SlashCommandLoaded? OnSlashCommandLoaded; + public ActionLoaded? OnActionLoaded; public static List Commands { get; private set; } = new List(); public static List Events { get; private set; } = new List(); public static List SlashCommands { get; private set; } = new List(); + public static List Actions { get; private set; } = new List(); public PluginLoader(DiscordSocketClient discordSocketClient) { @@ -34,9 +40,7 @@ public class PluginLoader { Application.CurrentApplication.Logger.Log("Loading plugins...", this); - var loader = new Loader(Application.CurrentApplication.ApplicationEnvironmentVariables["PluginFolder"], "dll"); - - //await this.ResetSlashCommands(); + var loader = new Loader(); loader.OnFileLoadedException += FileLoadedException; loader.OnPluginLoaded += OnPluginLoaded; @@ -53,6 +57,17 @@ public class PluginLoader { switch (result.PluginType) { + case PluginType.ACTION: + ICommandAction action = (ICommandAction)result.Plugin; + if (action.RunType == InternalActionRunType.ON_STARTUP || action.RunType == InternalActionRunType.BOTH) + action.ExecuteStartup(); + + if(action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH) + Actions.Add(action); + + OnActionLoaded?.Invoke(result); + + break; case PluginType.COMMAND: Commands.Add((DBCommand)result.Plugin); OnCommandLoaded?.Invoke(result); diff --git a/DiscordBotCore/Online/Helpers/OnlineFunctions.cs b/DiscordBotCore/Online/Helpers/OnlineFunctions.cs index e13e134..9134a1b 100644 --- a/DiscordBotCore/Online/Helpers/OnlineFunctions.cs +++ b/DiscordBotCore/Online/Helpers/OnlineFunctions.cs @@ -9,6 +9,44 @@ namespace DiscordBotCore.Online.Helpers; internal static class OnlineFunctions { + + /// + /// Copy one Stream to another + /// + /// The base stream + /// The destination stream + /// The buffer to read + /// The progress + /// The cancellation token + /// Triggered if any is empty + /// Triggered if is less then or equal to 0 + /// Triggered if is not readable + /// Triggered in is not writable + public static async Task CopyToOtherStreamAsync( + this Stream stream, Stream destination, int bufferSize, + IProgress? progress = null, + CancellationToken cancellationToken = default) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); + if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable."); + if (!destination.CanWrite) + throw new ArgumentException("Destination stream is not writable", nameof(destination)); + + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) + .ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); + } + } + + /// /// Downloads a and saves it to another . /// diff --git a/DiscordBotCore/Online/PluginManager.cs b/DiscordBotCore/Online/PluginManager.cs index 0d8086b..7d211ce 100644 --- a/DiscordBotCore/Online/PluginManager.cs +++ b/DiscordBotCore/Online/PluginManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.IO; using System.Linq; @@ -88,7 +89,11 @@ public class PluginManager public async Task AppendPluginToDatabase(PluginInfo pluginData) { List installedPlugins = await JsonManager.ConvertFromJson>(await File.ReadAllTextAsync(Application.CurrentApplication.PluginDatabase)); - + foreach (var dependency in pluginData.ListOfDependancies) + { + pluginData.ListOfDependancies[dependency.Key] = GenerateDependencyLocation(pluginData.PluginName, dependency.Value); + } + installedPlugins.Add(pluginData); await JsonManager.SaveToJsonFile(Application.CurrentApplication.PluginDatabase, installedPlugins); } @@ -123,15 +128,19 @@ public class PluginManager public async Task MarkPluginToUninstall(string pluginName) { - List installedPlugins = await GetInstalledPlugins(); - PluginInfo? info = installedPlugins.Find(info => info.PluginName == pluginName); + IEnumerable installedPlugins = await GetInstalledPlugins(); + IEnumerable info = installedPlugins.Where(info => info.PluginName == pluginName).AsEnumerable(); - if(info == null) + if(!info.Any()) return false; - await RemovePluginFromDatabase(pluginName); - info.IsMarkedToUninstall = true; - await AppendPluginToDatabase(info); + foreach (var item in info) + { + await RemovePluginFromDatabase(item.PluginName); + item.IsMarkedToUninstall = true; + await AppendPluginToDatabase(item); + } + return true; @@ -139,11 +148,11 @@ public class PluginManager public async Task UninstallMarkedPlugins() { - List installedPlugins = await GetInstalledPlugins(); - foreach(PluginInfo plugin in installedPlugins) - { - if(!plugin.IsMarkedToUninstall) continue; + IEnumerable installedPlugins = (await GetInstalledPlugins()).AsEnumerable(); + IEnumerable pluginsToRemove = installedPlugins.Where(plugin => plugin.IsMarkedToUninstall).AsEnumerable(); + foreach (var plugin in pluginsToRemove) + { await UninstallPlugin(plugin); } } @@ -171,21 +180,6 @@ public class PluginManager throw new Exception("Dependency not found"); } - public async Task GetDependencyLocation(string pluginName, string dependencyName) - { - PluginOnlineInfo? pluginData = await GetPluginDataByName(pluginName); - - if(pluginData == null) - throw new Exception("Plugin not found"); - - var dependency = pluginData.Dependencies.Find(dep => dep.DependencyName == dependencyName); - - if(dependency == null) - throw new Exception("Dependency not found"); - - return dependency.DownloadLocation; - } - public string GenerateDependencyLocation(string pluginName, string dependencyName) { return Path.Combine(Environment.CurrentDirectory, $"Libraries/{pluginName}/{dependencyName}"); diff --git a/DiscordBotCore/Others/Actions/InternalActionsManager.cs b/DiscordBotCore/Others/Actions/InternalActionsManager.cs index f6d01de..1ca470d 100644 --- a/DiscordBotCore/Others/Actions/InternalActionsManager.cs +++ b/DiscordBotCore/Others/Actions/InternalActionsManager.cs @@ -8,31 +8,36 @@ namespace DiscordBotCore.Others.Actions; public class InternalActionManager { - public Dictionary Actions = new(); - - private readonly ActionsLoader _loader; - - public InternalActionManager(string path, string extension) - { - _loader = new ActionsLoader(path, extension); - } + private Dictionary Actions = new(); public async Task Initialize() { - var loadedActions = await _loader.Load(); + Actions.Clear(); + PluginLoader.Actions.ForEach(action => + { + if (action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH) + { + if (this.Actions.ContainsKey(action.ActionName)) + return; // ingore duplicates - if (loadedActions == null) - return; - - foreach (var action in loadedActions) - Actions.TryAdd(action.ActionName, action); - + this.Actions.Add(action.ActionName, action); + } + }); } - public async Task Refresh() + public IReadOnlyCollection GetActions() { - Actions.Clear(); - await Initialize(); + return Actions.Values; + } + + public bool Exists(string actionName) + { + return Actions.ContainsKey(actionName); + } + + public ICommandAction GetAction(string actionName) + { + return Actions[actionName]; } public async Task Execute(string actionName, params string[]? args) diff --git a/DiscordBotCore/Others/Enums.cs b/DiscordBotCore/Others/Enums.cs index 381daa1..f48de1b 100644 --- a/DiscordBotCore/Others/Enums.cs +++ b/DiscordBotCore/Others/Enums.cs @@ -40,5 +40,6 @@ public enum PluginType UNKNOWN, COMMAND, EVENT, - SLASH_COMMAND + SLASH_COMMAND, + ACTION } diff --git a/DiscordBotCore/Others/Functions.cs b/DiscordBotCore/Others/Functions.cs deleted file mode 100644 index 019cd1d..0000000 --- a/DiscordBotCore/Others/Functions.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Discord; - -namespace DiscordBotCore.Others; - -/// -/// A special class with functions -/// -public static class Functions -{ - /// - /// The location for the Resources folder - /// String: ./Data/Resources/ - /// - public static string dataFolder => Application.CurrentApplication.DataFolder; - - public static Color RandomColor - { - get - { - var random = new Random(); - return new Color(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)); - } - } - - /// - /// Copy one Stream to another - /// - /// The base stream - /// The destination stream - /// The buffer to read - /// The progress - /// The cancellation token - /// Triggered if any is empty - /// Triggered if is less then or equal to 0 - /// Triggered if is not readable - /// Triggered in is not writable - public static async Task CopyToOtherStreamAsync( - this Stream stream, Stream destination, int bufferSize, - IProgress? progress = null, - CancellationToken cancellationToken = default) - { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable."); - if (!destination.CanWrite) - throw new ArgumentException("Destination stream is not writable", nameof(destination)); - - var buffer = new byte[bufferSize]; - long totalBytesRead = 0; - int bytesRead; - while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken) - .ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - totalBytesRead += bytesRead; - progress?.Report(totalBytesRead); - } - } - - - public static T SelectRandomValueOf() - { - var enums = Enum.GetValues(typeof(T)); - var random = new Random(); - return (T)enums.GetValue(random.Next(enums.Length)); - } - - public static T RandomValue(this T[] values) - { - Random random = new(); - return values[random.Next(values.Length)]; - } - - public static string ToResourcesPath(this string path) - { - return Path.Combine(dataFolder, path); - } -}