mirror of
https://github.com/RPCS3/discord-bot.git
synced 2026-01-31 01:25:22 +01:00
186 lines
9.0 KiB
C#
186 lines
9.0 KiB
C#
using System.Net;
|
|
using System.Text.RegularExpressions;
|
|
using DSharpPlus.Commands.EventArgs;
|
|
using DSharpPlus.Commands.Exceptions;
|
|
using DSharpPlus.Commands.Processors.TextCommands;
|
|
using DSharpPlus.Exceptions;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace CompatBot.Commands.Processors;
|
|
|
|
internal static partial class CommandErroredHandler
|
|
{
|
|
[GeneratedRegex(
|
|
@"^\s*(am I|(are|is|do(es)|did|can(?!\s+of\s+)|should|must|have)(n't)?|shall|shan't|may|will|won't)\b",
|
|
RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase
|
|
)]
|
|
private static partial Regex BinaryQuestion();
|
|
|
|
// CommandsExtension.DefaultCommandErrorHandlerAsync()
|
|
public static async Task OnError(CommandsExtension sender, CommandErroredEventArgs eventArgs)
|
|
{
|
|
StringBuilder stringBuilder = new();
|
|
DiscordMessageBuilder messageBuilder = new();
|
|
|
|
if (eventArgs is
|
|
{
|
|
Exception: CommandNotFoundException or CommandNotExecutableException,
|
|
Context: TextCommandContext tctx
|
|
})
|
|
{
|
|
// if it has a command prefix, check that it's not a random string of punctuation
|
|
if ((tctx.Prefix == Config.CommandPrefix
|
|
|| tctx.Prefix == Config.AutoRemoveCommandPrefix)
|
|
&& tctx.Message.Content[tctx.Prefix.Length ..] is {Length: >0} args
|
|
&& !char.IsPunctuation(args[0]))
|
|
{
|
|
try
|
|
{
|
|
await tctx.Message.DeleteAsync().ConfigureAwait(false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Config.Log.Warn(e, "Failed to remove message with unknown text command");
|
|
}
|
|
try
|
|
{
|
|
var dm = await tctx.CreateDmAsync().ConfigureAwait(false);
|
|
await dm.SendMessageAsync("""
|
|
You have tried to use an outdated bot command, please use Discord Application Command instead.
|
|
You can access them through `⊕` > `Use Apps` on desktop, or the gamepad button on mobile.
|
|
Alternately you can open the list of App Commands by typing `/` prefix.
|
|
"""
|
|
).ConfigureAwait(false);
|
|
} catch {}
|
|
}
|
|
else
|
|
{
|
|
if (tctx.Message.Content is string msgTxt
|
|
&& (msgTxt.EndsWith('?') || BinaryQuestion().IsMatch(msgTxt.AsSpan(tctx.Prefix!.Length)))
|
|
&& tctx.Extension.Commands.TryGetValue("8ball", out var cmd))
|
|
{
|
|
await using var scope = tctx.Extension.ServiceProvider.CreateAsyncScope();
|
|
var updatedContext = new TextCommandContext()
|
|
{
|
|
User = tctx.User,
|
|
Channel = tctx.Channel,
|
|
Message = tctx.Message,
|
|
PrefixLength = tctx.Prefix!.Length,
|
|
|
|
Command = cmd,
|
|
Arguments = new Dictionary<CommandParameter, object?>()
|
|
{
|
|
[new() { Name = "question", Type = typeof(string), Parent = cmd }] = msgTxt[tctx.Prefix!.Length ..].Trim()
|
|
},
|
|
Extension = tctx.Extension,
|
|
ServiceScope = scope,
|
|
};
|
|
try
|
|
{
|
|
await tctx.Extension.CommandExecutor.ExecuteAsync(updatedContext).ConfigureAwait(false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Config.Log.Warn(e, "Failed to execute custom 8ball command");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Error message
|
|
stringBuilder.Append(eventArgs.Exception switch
|
|
{
|
|
CommandNotFoundException commandNotFoundException
|
|
=> $"Command ``{commandNotFoundException.CommandName}`` was not found.",
|
|
|
|
CommandRegistrationFailedException
|
|
=> "Application commands failed to register.",
|
|
|
|
ArgumentParseException { ConversionResult.Value: not null } argumentParseException
|
|
=> $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: ``{argumentParseException.ConversionResult.Value.ToString() ?? "<null>"}`` is not a valid value. {argumentParseException.Message}",
|
|
|
|
ArgumentParseException argumentParseException
|
|
=> $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: {argumentParseException.Message}",
|
|
|
|
ChecksFailedException { Errors.Count: 1 } checksFailedException
|
|
=> $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
|
|
|
|
ChecksFailedException checksFailedException
|
|
=> $"""
|
|
The following context checks failed: ```
|
|
{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}
|
|
```
|
|
""",
|
|
|
|
ParameterChecksFailedException { Errors.Count: 1 } checksFailedException
|
|
=> $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
|
|
|
|
ParameterChecksFailedException checksFailedException
|
|
=> $"""
|
|
The following context checks failed: ```
|
|
{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}
|
|
```
|
|
""",
|
|
|
|
DiscordException { Response.StatusCode: >= (HttpStatusCode)500 and < (HttpStatusCode)600 } discordException
|
|
=> $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? "No further information was provided."}",
|
|
|
|
DiscordException { Response: not null } discordException
|
|
=> $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? discordException.Message}",
|
|
|
|
_ => $"An unexpected error occurred: {eventArgs.Exception.Message}",
|
|
});
|
|
|
|
// Stack trace
|
|
if (!string.IsNullOrWhiteSpace(eventArgs.Exception.StackTrace))
|
|
{
|
|
// If the stack trace can fit inside a codeblock
|
|
if (8 + eventArgs.Exception.StackTrace.Length + stringBuilder.Length <= 2000)
|
|
{
|
|
stringBuilder.Append($"```\n{eventArgs.Exception.StackTrace}\n```");
|
|
messageBuilder.WithContent(stringBuilder.ToString());
|
|
}
|
|
// If the exception message exceeds the message character limit, cram it all into an attatched file with a simple message in the content.
|
|
else if (stringBuilder.Length >= 2000)
|
|
{
|
|
messageBuilder.WithContent("Exception Message exceeds character limit, see attached file.");
|
|
string formattedFile = $"{stringBuilder}{Environment.NewLine}{Environment.NewLine}Stack Trace:{Environment.NewLine}{eventArgs.Exception.StackTrace}";
|
|
messageBuilder.AddFile(
|
|
"MessageAndStackTrace.txt",
|
|
Config.MemoryStreamManager.GetStream(Encoding.UTF8.GetBytes(formattedFile)),
|
|
AddFileOptions.CloseStream
|
|
);
|
|
}
|
|
// Otherwise, display the exception message in the content and the trace in an attached file
|
|
else
|
|
{
|
|
messageBuilder.WithContent(stringBuilder.ToString());
|
|
messageBuilder.AddFile(
|
|
"StackTrace.txt",
|
|
Config.MemoryStreamManager.GetStream(Encoding.UTF8.GetBytes(eventArgs.Exception.StackTrace)),
|
|
AddFileOptions.CloseStream
|
|
);
|
|
}
|
|
}
|
|
// If no stack trace, and the message is still too long, attatch a file with the message and use a simple message in the content.
|
|
else if (stringBuilder.Length >= 2000)
|
|
{
|
|
messageBuilder.WithContent("Exception Message exceeds character limit, see attached file.");
|
|
messageBuilder.AddFile(
|
|
"Message.txt",
|
|
Config.MemoryStreamManager.GetStream(Encoding.UTF8.GetBytes(stringBuilder.ToString())),
|
|
AddFileOptions.CloseStream
|
|
);
|
|
}
|
|
// Otherwise, if no stack trace and the Exception message will fit, send the message as content
|
|
else
|
|
messageBuilder.WithContent(stringBuilder.ToString());
|
|
|
|
if (eventArgs.Context is SlashCommandContext { Interaction.ResponseState: not DiscordInteractionResponseState.Unacknowledged })
|
|
await eventArgs.Context.FollowupAsync(new DiscordFollowupMessageBuilder(messageBuilder).AsEphemeral());
|
|
else
|
|
await eventArgs.Context.RespondAsync(new DiscordInteractionResponseBuilder(messageBuilder).AsEphemeral());
|
|
}
|
|
} |