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;