Refactor command system
This commit is contained in:
@@ -12,6 +12,10 @@ using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using PluralKit.Bot.Commands;
|
||||
using PluralKit.Bot.CommandSystem;
|
||||
|
||||
using Sentry;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
@@ -84,23 +88,18 @@ namespace PluralKit.Bot
|
||||
ExclusiveBulkDelete = true
|
||||
}))
|
||||
.AddSingleton<Bot>()
|
||||
|
||||
.AddSingleton(_ => new CommandService(new CommandServiceConfig
|
||||
{
|
||||
CaseSensitiveCommands = false,
|
||||
QuotationMarkAliasMap = new Dictionary<char, char>
|
||||
{
|
||||
{'"', '"'},
|
||||
{'\'', '\''},
|
||||
{'‘', '’'},
|
||||
{'“', '”'},
|
||||
{'„', '‟'},
|
||||
},
|
||||
// We're already asyncing stuff by forking off at the client event handlers
|
||||
// So adding an additional layer of forking is pointless
|
||||
// and leads to the service scope being disposed of prematurely
|
||||
DefaultRunMode = RunMode.Sync
|
||||
}))
|
||||
.AddTransient<CommandTree>()
|
||||
|
||||
.AddTransient<SystemCommands>()
|
||||
.AddTransient<MemberCommands>()
|
||||
.AddTransient<SwitchCommands>()
|
||||
.AddTransient<LinkCommands>()
|
||||
.AddTransient<APICommands>()
|
||||
.AddTransient<ImportExportCommands>()
|
||||
.AddTransient<HelpCommands>()
|
||||
.AddTransient<ModCommands>()
|
||||
.AddTransient<MiscCommands>()
|
||||
|
||||
.AddTransient<EmbedService>()
|
||||
.AddTransient<ProxyService>()
|
||||
.AddTransient<LogChannelService>()
|
||||
@@ -133,29 +132,22 @@ namespace PluralKit.Bot
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
private DiscordShardedClient _client;
|
||||
private CommandService _commands;
|
||||
private Timer _updateTimer;
|
||||
private IMetrics _metrics;
|
||||
private PeriodicStatCollector _collector;
|
||||
private ILogger _logger;
|
||||
|
||||
public Bot(IServiceProvider services, IDiscordClient client, CommandService commands, IMetrics metrics, PeriodicStatCollector collector, ILogger logger)
|
||||
public Bot(IServiceProvider services, IDiscordClient client, IMetrics metrics, PeriodicStatCollector collector, ILogger logger)
|
||||
{
|
||||
_services = services;
|
||||
_client = client as DiscordShardedClient;
|
||||
_commands = commands;
|
||||
_metrics = metrics;
|
||||
_collector = collector;
|
||||
_logger = logger.ForContext<Bot>();
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
public Task Init()
|
||||
{
|
||||
_commands.AddTypeReader<PKSystem>(new PKSystemTypeReader());
|
||||
_commands.AddTypeReader<PKMember>(new PKMemberTypeReader());
|
||||
_commands.CommandExecuted += CommandExecuted;
|
||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||
|
||||
_client.ShardReady += ShardReady;
|
||||
_client.Log += FrameworkLog;
|
||||
|
||||
@@ -171,6 +163,8 @@ namespace PluralKit.Bot
|
||||
_client.ReactionAdded += (msg, channel, reaction) => HandleEvent(s => s.AddReactionAddedBreadcrumb(msg, channel, reaction), eh => eh.HandleReactionAdded(msg, channel, reaction));
|
||||
_client.MessageDeleted += (msg, channel) => HandleEvent(s => s.AddMessageDeleteBreadcrumb(msg, channel), eh => eh.HandleMessageDeleted(msg, channel));
|
||||
_client.MessagesBulkDeleted += (msgs, channel) => HandleEvent(s => s.AddMessageBulkDeleteBreadcrumb(msgs, channel), eh => eh.HandleMessagesBulkDelete(msgs, channel));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task FrameworkLog(LogMessage msg)
|
||||
@@ -224,39 +218,39 @@ namespace PluralKit.Bot
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task CommandExecuted(Optional<CommandInfo> cmd, ICommandContext ctx, IResult _result)
|
||||
{
|
||||
var svc = ((PKCommandContext) ctx).ServiceProvider;
|
||||
var id = svc.GetService<EventIdProvider>();
|
||||
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.CommandsRun);
|
||||
|
||||
// TODO: refactor this entire block, it's fugly.
|
||||
if (!_result.IsSuccess) {
|
||||
if (_result.Error == CommandError.Unsuccessful || _result.Error == CommandError.Exception) {
|
||||
// If this is a PKError (ie. thrown deliberately), show user facing message
|
||||
// If not, log as error
|
||||
var exception = (_result as ExecuteResult?)?.Exception;
|
||||
if (exception is PKError) {
|
||||
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {exception.Message}");
|
||||
} else if (exception is TimeoutException) {
|
||||
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} Operation timed out. Try being faster next time :)");
|
||||
} else if (_result is PreconditionResult)
|
||||
{
|
||||
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
||||
} else
|
||||
{
|
||||
await ctx.Message.Channel.SendMessageAsync(
|
||||
$"{Emojis.Error} Internal error occurred. Please join the support server (<https://discord.gg/PczBt78>), and send the developer this ID: `{id.EventId}`.");
|
||||
HandleRuntimeError((_result as ExecuteResult?)?.Exception, svc);
|
||||
}
|
||||
} else if ((_result.Error == CommandError.BadArgCount || _result.Error == CommandError.MultipleMatches) && cmd.IsSpecified) {
|
||||
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}\n**Usage: **pk;{cmd.Value.Remarks}");
|
||||
} else if (_result.Error == CommandError.UnknownCommand || _result.Error == CommandError.UnmetPrecondition || _result.Error == CommandError.ObjectNotFound) {
|
||||
await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// private async Task CommandExecuted(Optional<CommandInfo> cmd, ICommandContext ctx, IResult _result)
|
||||
// {
|
||||
// var svc = ((PKCommandContext) ctx).ServiceProvider;
|
||||
// var id = svc.GetService<EventIdProvider>();
|
||||
//
|
||||
// _metrics.Measure.Meter.Mark(BotMetrics.CommandsRun);
|
||||
//
|
||||
// // TODO: refactor this entire block, it's fugly.
|
||||
// if (!_result.IsSuccess) {
|
||||
// if (_result.Error == CommandError.Unsuccessful || _result.Error == CommandError.Exception) {
|
||||
// // If this is a PKError (ie. thrown deliberately), show user facing message
|
||||
// // If not, log as error
|
||||
// var exception = (_result as ExecuteResult?)?.Exception;
|
||||
// if (exception is PKError) {
|
||||
// await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {exception.Message}");
|
||||
// } else if (exception is TimeoutException) {
|
||||
// await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} Operation timed out. Try being faster next time :)");
|
||||
// } else if (_result is PreconditionResult)
|
||||
// {
|
||||
// await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
||||
// } else
|
||||
// {
|
||||
// await ctx.Message.Channel.SendMessageAsync(
|
||||
// $"{Emojis.Error} Internal error occurred. Please join the support server (<https://discord.gg/PczBt78>), and send the developer this ID: `{id.EventId}`.");
|
||||
// HandleRuntimeError((_result as ExecuteResult?)?.Exception, svc);
|
||||
// }
|
||||
// } else if ((_result.Error == CommandError.BadArgCount || _result.Error == CommandError.MultipleMatches) && cmd.IsSpecified) {
|
||||
// await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}\n**Usage: **pk;{cmd.Value.Remarks}");
|
||||
// } else if (_result.Error == CommandError.UnknownCommand || _result.Error == CommandError.UnmetPrecondition || _result.Error == CommandError.ObjectNotFound) {
|
||||
// await ctx.Message.Channel.SendMessageAsync($"{Emojis.Error} {_result.ErrorReason}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private Task HandleEvent(Action<Scope> breadcrumbFactory, Func<PKEventHandler, Task> handler)
|
||||
{
|
||||
@@ -309,47 +303,51 @@ namespace PluralKit.Bot
|
||||
}
|
||||
|
||||
class PKEventHandler {
|
||||
private CommandService _commands;
|
||||
private ProxyService _proxy;
|
||||
private ILogger _logger;
|
||||
private IMetrics _metrics;
|
||||
private DiscordShardedClient _client;
|
||||
private DbConnectionFactory _connectionFactory;
|
||||
private IServiceProvider _services;
|
||||
private CommandTree _tree;
|
||||
|
||||
public PKEventHandler(CommandService commands, ProxyService proxy, ILogger logger, IMetrics metrics, IDiscordClient client, DbConnectionFactory connectionFactory, IServiceProvider services)
|
||||
public PKEventHandler(ProxyService proxy, ILogger logger, IMetrics metrics, IDiscordClient client, DbConnectionFactory connectionFactory, IServiceProvider services, CommandTree tree)
|
||||
{
|
||||
_commands = commands;
|
||||
_proxy = proxy;
|
||||
_logger = logger;
|
||||
_metrics = metrics;
|
||||
_client = (DiscordShardedClient) client;
|
||||
_connectionFactory = connectionFactory;
|
||||
_services = services;
|
||||
_tree = tree;
|
||||
}
|
||||
|
||||
public async Task HandleMessage(SocketMessage msg)
|
||||
public async Task HandleMessage(SocketMessage arg)
|
||||
{
|
||||
RegisterMessageMetrics(msg);
|
||||
RegisterMessageMetrics(arg);
|
||||
|
||||
// Ignore system messages (member joined, message pinned, etc)
|
||||
var arg = msg as SocketUserMessage;
|
||||
if (arg == null) return;
|
||||
var msg = arg as SocketUserMessage;
|
||||
if (msg == null) return;
|
||||
|
||||
// Ignore bot messages
|
||||
if (arg.Author.IsBot || arg.Author.IsWebhook) return;
|
||||
if (msg.Author.IsBot || msg.Author.IsWebhook) return;
|
||||
|
||||
int argPos = 0;
|
||||
int argPos = -1;
|
||||
// Check if message starts with the command prefix
|
||||
if (arg.HasStringPrefix("pk;", ref argPos, StringComparison.OrdinalIgnoreCase) ||
|
||||
arg.HasStringPrefix("pk!", ref argPos, StringComparison.OrdinalIgnoreCase) ||
|
||||
arg.HasMentionPrefix(_client.CurrentUser, ref argPos))
|
||||
if (msg.Content.StartsWith("pk;")) argPos = 3;
|
||||
else if (msg.Content.StartsWith("pk!")) argPos = 3;
|
||||
else if (Utils.HasMentionPrefix(msg.Content, ref argPos, out var id)) // Set argPos to the proper value
|
||||
if (id != _client.CurrentUser.Id) // But undo it if it's someone else's ping
|
||||
argPos = -1;
|
||||
|
||||
if (argPos > -1)
|
||||
{
|
||||
_logger.Debug("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id);
|
||||
|
||||
// Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string
|
||||
var trimStartLengthDiff = arg.Content.Substring(argPos).Length -
|
||||
arg.Content.Substring(argPos).TrimStart().Length;
|
||||
var trimStartLengthDiff = msg.Content.Substring(argPos).Length -
|
||||
msg.Content.Substring(argPos).TrimStart().Length;
|
||||
argPos += trimStartLengthDiff;
|
||||
|
||||
// If it does, fetch the sender's system (because most commands need that) into the context,
|
||||
@@ -359,21 +357,20 @@ namespace PluralKit.Bot
|
||||
using (var conn = await _connectionFactory.Obtain())
|
||||
system = await conn.QueryFirstOrDefaultAsync<PKSystem>(
|
||||
"select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system",
|
||||
new {Id = arg.Author.Id});
|
||||
|
||||
await _commands.ExecuteAsync(new PKCommandContext(_client, arg, system, _services), argPos,
|
||||
_services);
|
||||
new {Id = msg.Author.Id});
|
||||
|
||||
await _tree.ExecuteCommand(new Context(_services, msg, argPos, system));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not, try proxying anyway
|
||||
try
|
||||
{
|
||||
await _proxy.HandleMessageAsync(arg);
|
||||
await _proxy.HandleMessageAsync(msg);
|
||||
}
|
||||
catch (PKError e)
|
||||
{
|
||||
await msg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}");
|
||||
await arg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user