diff --git a/BrackeysBot.API/Attributes/RequireMentionPrefix.cs b/BrackeysBot.API/Attributes/RequireMentionPrefix.cs new file mode 100644 index 0000000..405aa01 --- /dev/null +++ b/BrackeysBot.API/Attributes/RequireMentionPrefix.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using DisCatSharp.CommandsNext; +using DisCatSharp.CommandsNext.Attributes; + +namespace BrackeysBot.API.Attributes; + +/// +/// Defines that usage of this command must require a bot mention prefix rather than the plugin's defined prefix. +/// +public sealed class RequireMentionPrefix : CheckBaseAttribute +{ + /// + public override Task ExecuteCheckAsync(CommandContext ctx, bool help) + { + return Task.FromResult(MentionUtility.TryParseUser(ctx.Prefix[..^1], out ulong id) && id == ctx.Client.CurrentUser.Id); + } +} diff --git a/BrackeysBot/Plugins/SimplePluginManager.cs b/BrackeysBot/Plugins/SimplePluginManager.cs index 34e0b61..725e751 100644 --- a/BrackeysBot/Plugins/SimplePluginManager.cs +++ b/BrackeysBot/Plugins/SimplePluginManager.cs @@ -7,6 +7,7 @@ using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; +using BrackeysBot.API; using BrackeysBot.API.Exceptions; using BrackeysBot.API.Plugins; using BrackeysBot.ArgumentConverters; @@ -15,6 +16,9 @@ using DisCatSharp; using DisCatSharp.CommandsNext; using DisCatSharp.CommandsNext.Converters; +using DisCatSharp.CommandsNext.Exceptions; +using DisCatSharp.Entities; +using DisCatSharp.EventArgs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -208,10 +212,11 @@ public IPlugin LoadPlugin(string name) SetupPluginDataDirectory(pluginInfo, instance); SetupPluginConfiguration(instance); SetupPluginServices(instance, pluginInfo, pluginType); + CommandsNextExtension? commandsNext = SetupPluginCommands(instance); instance.OnLoad().GetAwaiter().GetResult(); - if (instance.DiscordClient?.GetCommandsNext() is { } commandsNext) + if (commandsNext is not null) { commandsNext.UnregisterConverter(); commandsNext.RegisterConverter(new TimeSpanArgumentConverter()); @@ -312,6 +317,37 @@ public void UnloadPlugin(IPlugin plugin) Logger.Info(string.Format(LoggerMessages.UnloadedPlugin, plugin.PluginInfo.Name, plugin.PluginInfo.Version)); } + private static Task ClientOnMessageCreated(MonoPlugin plugin, DiscordClient sender, MessageCreateEventArgs e) + { + CommandsNextExtension? commandsNext = sender.GetCommandsNext(); + if (commandsNext is null) return Task.CompletedTask; + + DiscordMessage? message = e.Message; + if (message.Content is not {Length: > 0} content) return Task.CompletedTask; + + string prefix = plugin.Configuration.Get("discord.prefix") ?? "[]"; + int commandStart = message.GetStringPrefixLength(MentionUtility.MentionUser(sender.CurrentUser.Id, false) + ' '); + if (commandStart == -1) + { + commandStart = message.GetStringPrefixLength(MentionUtility.MentionUser(sender.CurrentUser.Id) + ' '); + if (commandStart == -1) + { + commandStart = message.GetStringPrefixLength(prefix); + if (commandStart == -1) return Task.CompletedTask; + } + } + + prefix = content[..commandStart]; + string commandString = content[commandStart..]; + + Command? command = commandsNext.FindCommand(commandString, out string? args); + if (command is null) return Task.CompletedTask; + + CommandContext context = commandsNext.CreateContext(message, prefix, command, args); + Task.Run(async () => await commandsNext.ExecuteCommandAsync(context)); + return Task.CompletedTask; + } + private static Type GetPluginType(string name, Assembly assembly, out PluginAttribute pluginAttribute) { Type[] pluginTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(MonoPlugin))).ToArray(); @@ -366,6 +402,19 @@ private IEnumerable EnumeratePluginDependencies(Type pluginType) return new PluginInfo.PluginAuthorInfo(authorAttribute.Name, authorAttribute.Email, authorAttribute.Url); } + private static CommandsNextExtension? SetupPluginCommands(MonoPlugin plugin) + { + if (plugin.ServiceProvider.GetService() is not { } client) return null; + + client.MessageCreated += (sender, e) => ClientOnMessageCreated(plugin, sender, e); + + return client.UseCommandsNext(new CommandsNextConfiguration + { + ServiceProvider = plugin.ServiceProvider, + UseDefaultCommandHandler = false + }); + } + private void SetupPluginDataDirectory(PluginInfo pluginInfo, MonoPlugin instance) { var dataDirectory = new DirectoryInfo(Path.Combine(PluginDirectory.FullName, pluginInfo.Name)); @@ -403,6 +452,9 @@ private static void RegisterCommandEvents(IPlugin plugin, CommandsNextExtension commandsNext.CommandErrored += (_, args) => { CommandContext context = args.Context; + if (context?.Command is null) return Task.CompletedTask; + if (args.Exception is ChecksFailedException) return Task.CompletedTask; // no need to log ChecksFailedException + var commandName = $"{context.Prefix}{context.Command.Name}"; plugin.Logger.Error(args.Exception, $"An exception was thrown when executing {commandName}"); return Task.CompletedTask;