Actions are now loaded together with all plugins. Called the LoadPlugins at startup
This commit is contained in:
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using DiscordBotCore.Others;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
public class Boot
|
||||
public class App
|
||||
{
|
||||
/// <summary>
|
||||
/// The bot prefix
|
||||
@@ -41,7 +41,7 @@ public class Boot
|
||||
/// </summary>
|
||||
/// <param name="botToken">The bot token</param>
|
||||
/// <param name="botPrefix">The bot prefix</param>
|
||||
public Boot(string botToken, string botPrefix)
|
||||
public App(string botToken, string botPrefix)
|
||||
{
|
||||
this.BotPrefix = botPrefix;
|
||||
this.BotToken = botToken;
|
||||
@@ -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<List<ICommandAction>?> Load()
|
||||
{
|
||||
Directory.CreateDirectory(_actionFolder);
|
||||
var files = Directory.GetFiles(_actionFolder, $"*.{_actionExtension}", SearchOption.AllDirectories);
|
||||
|
||||
var actions = new List<ICommandAction>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<DBEvent>();
|
||||
await LoadEverythingOfType<DBCommand>();
|
||||
await LoadEverythingOfType<DBSlashCommand>();
|
||||
await LoadEverythingOfType<ICommandAction>();
|
||||
}
|
||||
|
||||
private async Task LoadEverythingOfType<T>()
|
||||
@@ -77,6 +63,7 @@ internal class Loader
|
||||
DBEvent => PluginType.EVENT,
|
||||
DBCommand => PluginType.COMMAND,
|
||||
DBSlashCommand => PluginType.SLASH_COMMAND,
|
||||
ICommandAction => PluginType.ACTION,
|
||||
_ => PluginType.UNKNOWN
|
||||
};
|
||||
|
||||
|
||||
@@ -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<DBCommand> Commands { get; private set; } = new List<DBCommand>();
|
||||
public static List<DBEvent> Events { get; private set; } = new List<DBEvent>();
|
||||
public static List<DBSlashCommand> SlashCommands { get; private set; } = new List<DBSlashCommand>();
|
||||
public static List<ICommandAction> Actions { get; private set; } = new List<ICommandAction>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -9,6 +9,44 @@ namespace DiscordBotCore.Online.Helpers;
|
||||
|
||||
internal static class OnlineFunctions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Copy one Stream to another <see langword="async" />
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream</param>
|
||||
/// <param name="destination">The destination stream</param>
|
||||
/// <param name="bufferSize">The buffer to read</param>
|
||||
/// <param name="progress">The progress</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
|
||||
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
|
||||
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
|
||||
public static async Task CopyToOtherStreamAsync(
|
||||
this Stream stream, Stream destination, int bufferSize,
|
||||
IProgress<long>? 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a <see cref="Stream" /> and saves it to another <see cref="Stream" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -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<PluginInfo> installedPlugins = await JsonManager.ConvertFromJson<List<PluginInfo>>(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<bool> MarkPluginToUninstall(string pluginName)
|
||||
{
|
||||
List<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
PluginInfo? info = installedPlugins.Find(info => info.PluginName == pluginName);
|
||||
IEnumerable<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
IEnumerable<PluginInfo> 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<PluginInfo> installedPlugins = await GetInstalledPlugins();
|
||||
foreach(PluginInfo plugin in installedPlugins)
|
||||
{
|
||||
if(!plugin.IsMarkedToUninstall) continue;
|
||||
IEnumerable<PluginInfo> installedPlugins = (await GetInstalledPlugins()).AsEnumerable();
|
||||
IEnumerable<PluginInfo> 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<string> 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}");
|
||||
|
||||
@@ -8,31 +8,36 @@ namespace DiscordBotCore.Others.Actions;
|
||||
|
||||
public class InternalActionManager
|
||||
{
|
||||
public Dictionary<string, ICommandAction> Actions = new();
|
||||
|
||||
private readonly ActionsLoader _loader;
|
||||
|
||||
public InternalActionManager(string path, string extension)
|
||||
{
|
||||
_loader = new ActionsLoader(path, extension);
|
||||
}
|
||||
private Dictionary<string, ICommandAction> 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<ICommandAction> 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<bool> Execute(string actionName, params string[]? args)
|
||||
|
||||
@@ -40,5 +40,6 @@ public enum PluginType
|
||||
UNKNOWN,
|
||||
COMMAND,
|
||||
EVENT,
|
||||
SLASH_COMMAND
|
||||
SLASH_COMMAND,
|
||||
ACTION
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
/// <summary>
|
||||
/// A special class with functions
|
||||
/// </summary>
|
||||
public static class Functions
|
||||
{
|
||||
/// <summary>
|
||||
/// The location for the Resources folder
|
||||
/// String: ./Data/Resources/
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy one Stream to another <see langword="async" />
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream</param>
|
||||
/// <param name="destination">The destination stream</param>
|
||||
/// <param name="bufferSize">The buffer to read</param>
|
||||
/// <param name="progress">The progress</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
|
||||
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
|
||||
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
|
||||
public static async Task CopyToOtherStreamAsync(
|
||||
this Stream stream, Stream destination, int bufferSize,
|
||||
IProgress<long>? 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<T>()
|
||||
{
|
||||
var enums = Enum.GetValues(typeof(T));
|
||||
var random = new Random();
|
||||
return (T)enums.GetValue(random.Next(enums.Length));
|
||||
}
|
||||
|
||||
public static T RandomValue<T>(this T[] values)
|
||||
{
|
||||
Random random = new();
|
||||
return values[random.Next(values.Length)];
|
||||
}
|
||||
|
||||
public static string ToResourcesPath(this string path)
|
||||
{
|
||||
return Path.Combine(dataFolder, path);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user