using System.Net.Mime; 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; namespace DiscordBotCore.PluginManagement.Loading; public sealed class PluginLoader : IPluginLoader { private readonly DiscordSocketClient _DiscordClient; private readonly IPluginManager _PluginManager; private readonly ILogger _Logger; private readonly IConfiguration _Configuration; public delegate void CommandLoaded(IDbCommand eCommand); public delegate void EventLoaded(IDbEvent eEvent); public delegate void SlashCommandLoaded(IDbSlashCommand eSlashCommand); public CommandLoaded? OnCommandLoaded; public EventLoaded? OnEventLoaded; public SlashCommandLoaded? OnSlashCommandLoaded; public List Commands { get; private set; } = new List(); public List Events { get; private set; } = new List(); public List SlashCommands { get; private set; } = new List(); public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration, DiscordSocketClient discordSocketDiscordClient) { _PluginManager = pluginManager; _DiscordClient = discordSocketDiscordClient; _Logger = logger; _Configuration = configuration; } public async Task LoadPlugins() { 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(); } private void FileLoadedException(string fileName, Exception exception) { _Logger.LogException(exception, this); } private void InitializeDbCommand(IDbCommand command) { Commands.Add(command); OnCommandLoaded?.Invoke(command); } private void InitializeEvent(IDbEvent eEvent) { if (!TryStartEvent(eEvent)) { return; } Events.Add(eEvent); OnEventLoaded?.Invoke(eEvent); } 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); OnSlashCommandLoaded?.Invoke(slashCommand); }, HandleError ); } private void HandleError(Exception exception) { _Logger.LogException(exception, this); } private void OnPluginLoaded(PluginLoaderResult result) { result.Match( InitializeDbCommand, InitializeEvent, InitializeSlashCommand, HandleError ); } private bool TryStartEvent(IDbEvent? dbEvent) { try { if (dbEvent is null) { throw new ArgumentNullException(nameof(dbEvent)); } IDbEventExecutingArgument args = new DbEventExecutingArgument( _Logger, _DiscordClient, _Configuration.Get("prefix"), new DirectoryInfo(Path.Combine(_Configuration.Get("ResourcesPath"), dbEvent.Name))); dbEvent.Start(args); return true; } catch (Exception e) { _Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.Error); _Logger.LogException(e, typeof(PluginLoader)); return false; } } private async Task TryStartSlashCommand(IDbSlashCommand? dbSlashCommand) { try { if (dbSlashCommand is null) { return Result.Failure(new Exception("dbSlashCommand is null")); } if (_DiscordClient.Guilds.Count == 0) { return Result.Failure(new Exception("No guilds found")); } var builder = new SlashCommandBuilder(); builder.WithName(dbSlashCommand.Name); builder.WithDescription(dbSlashCommand.Description); builder.Options = dbSlashCommand.Options; 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); if (!result) { return Result.Failure($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}"); } } await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build()); return Result.Success(); } catch (Exception e) { return Result.Failure("Error starting slash command"); } } private async Task EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder) { SocketGuild? guild = _DiscordClient.GetGuild(guildId); if (guild is null) { _Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error); return false; } await guild.CreateApplicationCommandAsync(builder.Build()); return true; } }