aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/moderation/removeReactionEmoji.ts3
-rw-r--r--src/commands/utilities/viewRaw.ts6
-rw-r--r--src/context-menu-commands/user/userInfo.ts9
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts2
-rw-r--r--src/listeners/commands/commandBlocked.ts15
-rw-r--r--src/listeners/commands/commandError.ts11
-rw-r--r--src/listeners/commands/commandStarted.ts8
-rw-r--r--src/listeners/commands/messageBlocked.ts3
-rw-r--r--src/listeners/contextCommands/contextCommandBlocked.ts41
-rw-r--r--src/listeners/contextCommands/contextCommandError.ts123
-rw-r--r--src/listeners/contextCommands/contextCommandNotFound.ts16
-rw-r--r--src/listeners/contextCommands/contextCommandStarted.ts27
12 files changed, 240 insertions, 24 deletions
diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts
index b059bff..bc24db0 100644
--- a/src/commands/moderation/removeReactionEmoji.ts
+++ b/src/commands/moderation/removeReactionEmoji.ts
@@ -14,7 +14,8 @@ export default class RemoveReactionEmojiCommand extends BushCommand {
{
id: 'message',
description: 'The message to remove all the reactions of a certain emoji from.',
- type: 'guildMessage',
+ type: util.arg.union('message', 'messageLink'),
+ readableType: 'message|messageLink',
prompt: 'What message would you like to remove a reaction from?',
retry: '{error} Please pick a valid message.',
slashType: ApplicationCommandOptionType.String
diff --git a/src/commands/utilities/viewRaw.ts b/src/commands/utilities/viewRaw.ts
index 1da26f9..20e7272 100644
--- a/src/commands/utilities/viewRaw.ts
+++ b/src/commands/utilities/viewRaw.ts
@@ -14,8 +14,8 @@ export default class ViewRawCommand extends BushCommand {
{
id: 'message',
description: 'The message to view the raw content of.',
- type: util.arg.union('guildMessage', 'messageLink'),
- readableType: 'guildMessage|messageLink',
+ type: util.arg.union('message', 'messageLink'),
+ readableType: 'message|messageLink',
prompt: 'What message would you like to view?',
retry: '{error} Choose a valid message.',
slashType: ApplicationCommandOptionType.String
@@ -66,7 +66,7 @@ export default class ViewRawCommand extends BushCommand {
public override async exec(
message: BushMessage | BushSlashMessage,
args: {
- message: ArgType<'guildMessage'> | ArgType<'messageLink'>;
+ message: ArgType<'message'> | ArgType<'messageLink'>;
channel: OptionalArgType<'textChannel'> | OptionalArgType<'newsChannel'> | OptionalArgType<'threadChannel'>;
json: boolean;
js: boolean;
diff --git a/src/context-menu-commands/user/userInfo.ts b/src/context-menu-commands/user/userInfo.ts
index 5f59a30..8ecde92 100644
--- a/src/context-menu-commands/user/userInfo.ts
+++ b/src/context-menu-commands/user/userInfo.ts
@@ -15,9 +15,14 @@ export default class UserInfoContextMenuCommand extends ContextMenuCommand {
public override async exec(interaction: ContextMenuCommandInteraction) {
await interaction.deferReply({ ephemeral: true });
- const user = await client.users.fetch(interaction.targetId);
+ const user = await client.users.fetch(interaction.targetId).catch(() => null);
+ if (!user) return interaction.reply(`⁉ I couldn't find that user`);
+
const guild = interaction.guild as BushGuild;
- const member = await guild.members.fetch(interaction.targetId);
+
+ const member = await guild.members.fetch(interaction.targetId).catch(() => null);
+ if (!member) return interaction.reply(`${util.format.input(user.tag)} doesn't appear to be a member of this server anymore.`);
+
const userEmbed = await UserInfoCommand.makeUserInfoEmbed(user, member, guild);
return await interaction.editReply({ embeds: [userEmbed] });
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 5618903..5ccc283 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -347,7 +347,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
* Initializes the bot.
*/
public async init() {
- if (!process.version.startsWith('v17.')) {
+ if (parseInt(process.versions.node.split('.')[0]) <= 17) {
void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false));
process.exit(2);
}
diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts
index 5724d33..0d3c1d9 100644
--- a/src/listeners/commands/commandBlocked.ts
+++ b/src/listeners/commands/commandBlocked.ts
@@ -5,7 +5,8 @@ export default class CommandBlockedListener extends BushListener {
public constructor() {
super('commandBlocked', {
emitter: 'commandHandler',
- event: 'commandBlocked'
+ event: 'commandBlocked',
+ category: 'commands'
});
}
@@ -32,26 +33,26 @@ export default class CommandBlockedListener extends BushListener {
switch (reason) {
case reasons.OWNER: {
return await respond({
- content: `${util.emojis.error} Only my developers can run the ${util.format.input(command!.toString())} command.`,
+ content: `${util.emojis.error} Only my developers can run the ${util.format.input(command!.id)} command.`,
ephemeral: true
});
}
case reasons.SUPER_USER: {
return await respond({
- content: `${util.emojis.error} You must be a superuser to run the ${util.format.input(command!.toString())} command.`,
+ content: `${util.emojis.error} You must be a superuser to run the ${util.format.input(command!.id)} command.`,
ephemeral: true
});
}
case reasons.DISABLED_GLOBAL: {
return await respond({
- content: `${util.emojis.error} My developers disabled the ${util.format.input(command!.toString())} command.`,
+ content: `${util.emojis.error} My developers disabled the ${util.format.input(command!.id)} command.`,
ephemeral: true
});
}
case reasons.DISABLED_GUILD: {
return await respond({
content: `${util.emojis.error} The ${util.format.input(
- command!.toString()
+ command!.id
)} command is currently disabled in ${util.format.input(message.guild!.name)}.`,
ephemeral: true
});
@@ -89,7 +90,7 @@ export default class CommandBlockedListener extends BushListener {
});
const pretty = util.oxford(names, 'and');
return await respond({
- content: `${util.emojis.error} ${util.format.input(command!.toString())} can only be run in ${pretty}.`,
+ content: `${util.emojis.error} ${util.format.input(command!.id)} can only be run in ${pretty}.`,
ephemeral: true
});
}
@@ -99,7 +100,7 @@ export default class CommandBlockedListener extends BushListener {
const names = guilds!.map((g) => util.format.input(client.guilds.cache.get(g)?.name ?? g));
const pretty = util.oxford(names, 'and');
return await respond({
- content: `${util.emojis.error} ${util.format.input(command!.toString())} can only be run in ${pretty}.`,
+ content: `${util.emojis.error} ${util.format.input(command!.id)} can only be run in ${pretty}.`,
ephemeral: true
});
}
diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts
index e1acb00..d590e06 100644
--- a/src/listeners/commands/commandError.ts
+++ b/src/listeners/commands/commandError.ts
@@ -8,7 +8,8 @@ export default class CommandErrorListener extends BushListener {
public constructor() {
super('commandError', {
emitter: 'commandHandler',
- event: 'error'
+ event: 'error',
+ category: 'commands'
});
}
@@ -33,10 +34,10 @@ export default class CommandErrorListener extends BushListener {
'message.id': message.id,
'message.type': message.util.isSlash ? 'slash' : 'normal',
'message.parsed.content': message.util.parsed?.content,
- 'channel.id': (message.channel?.isDM() ? message.channel.recipient?.id : message.channel?.id) ?? '¯_(ツ)_/¯',
+ 'channel.id': (message.channel?.isDM() ? message.channel.recipient?.id : message.channel?.id) ?? '¯\\_(ツ)_/¯',
'channel.name': channel,
- 'guild.id': message.guild?.id ?? '¯_(ツ)_/¯',
- 'guild.name': message.guild?.name ?? '¯_(ツ)_/¯',
+ 'guild.id': message.guild?.id ?? '¯\\_(ツ)_/¯',
+ 'guild.name': message.guild?.name ?? '¯\\_(ツ)_/¯',
'environment': client.config.environment
}
});
@@ -232,7 +233,7 @@ export default class CommandErrorListener extends BushListener {
}
}
-class IFuckedUpError extends Error {
+export class IFuckedUpError extends Error {
public declare original: Error | any;
public declare newError: Error | any;
diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts
index 4d2f0ad..63b906b 100644
--- a/src/listeners/commands/commandStarted.ts
+++ b/src/listeners/commands/commandStarted.ts
@@ -21,11 +21,11 @@ export default class CommandStartedListener extends BushListener {
'message.id': message.id,
'message.type': message.util.isSlash ? 'slash' : 'normal',
'message.parsed.content': message.util.parsed?.content,
- 'channel.id': (message.channel.isDMBased() ? message.channel.recipient?.id : message.channel?.id) ?? '¯_(ツ)_/¯',
+ 'channel.id': (message.channel.isDMBased() ? message.channel.recipient?.id : message.channel?.id) ?? '¯\\_(ツ)_/¯',
'channel.name':
- (message.channel.isDMBased() ? message.channel.recipient?.tag : (<any>message.channel)?.name) ?? '¯_(ツ)_/¯',
- 'guild.id': message.guild?.id ?? '¯_(ツ)_/¯',
- 'guild.name': message.guild?.name ?? '¯_(ツ)_/¯',
+ (message.channel.isDMBased() ? message.channel.recipient?.tag : (<any>message.channel)?.name) ?? '¯\\_(ツ)_/¯',
+ 'guild.id': message.guild?.id ?? '¯\\_(ツ)_/¯',
+ 'guild.name': message.guild?.name ?? '¯\\_(ツ)_/¯',
'environment': client.config.environment
}
});
diff --git a/src/listeners/commands/messageBlocked.ts b/src/listeners/commands/messageBlocked.ts
index 1b969ed..5a2b10d 100644
--- a/src/listeners/commands/messageBlocked.ts
+++ b/src/listeners/commands/messageBlocked.ts
@@ -4,7 +4,8 @@ export default class MessageBlockedListener extends BushListener {
public constructor() {
super('messageBlocked', {
emitter: 'commandHandler',
- event: 'messageBlocked'
+ event: 'messageBlocked',
+ category: 'commands'
});
}
diff --git a/src/listeners/contextCommands/contextCommandBlocked.ts b/src/listeners/contextCommands/contextCommandBlocked.ts
new file mode 100644
index 0000000..7fe381e
--- /dev/null
+++ b/src/listeners/contextCommands/contextCommandBlocked.ts
@@ -0,0 +1,41 @@
+import { BushListener } from '#lib';
+import { ContextMenuCommandHandlerEvents } from 'discord-akairo';
+
+export default class ContextCommandBlockedListener extends BushListener {
+ public constructor() {
+ super('contextCommandBlocked', {
+ emitter: 'contextMenuCommandHandler',
+ event: 'blocked',
+ category: 'contextCommands'
+ });
+ }
+
+ public override async exec(...[interaction, command, reason]: ContextMenuCommandHandlerEvents['blocked']) {
+ void client.console.info(
+ `ContextCommandBlocked`,
+ `<<${interaction.user.tag}>> tried to run <<${command}>> but was blocked because <<${reason}>>.`,
+ true
+ );
+
+ switch (reason) {
+ case client.consts.BlockedReasons.OWNER: {
+ return await interaction.reply({
+ content: `${util.emojis.error} Only my developers can run the ${util.format.input(command!.id)} command.`,
+ ephemeral: true
+ });
+ }
+ case client.consts.BlockedReasons.SUPER_USER: {
+ return await interaction.reply({
+ content: `${util.emojis.error} You must be a superuser to run the ${util.format.input(command!.id)} command.`,
+ ephemeral: true
+ });
+ }
+ default: {
+ return await interaction.reply({
+ content: `${util.emojis.error} Command blocked with reason ${util.format.input(reason ?? 'unknown')}.`,
+ ephemeral: true
+ });
+ }
+ }
+ }
+}
diff --git a/src/listeners/contextCommands/contextCommandError.ts b/src/listeners/contextCommands/contextCommandError.ts
new file mode 100644
index 0000000..04a97ad
--- /dev/null
+++ b/src/listeners/contextCommands/contextCommandError.ts
@@ -0,0 +1,123 @@
+import { Severity } from '@sentry/types';
+import { ContextMenuCommand, ContextMenuCommandHandlerEvents } from 'discord-akairo';
+import { ContextMenuCommandInteraction, EmbedBuilder, GuildTextBasedChannel } from 'discord.js';
+import { BushListener } from '../../lib/extensions/discord-akairo/BushListener.js';
+import CommandErrorListener, { IFuckedUpError } from '../commands/commandError.js';
+
+export default class ContextCommandErrorListener extends BushListener {
+ public constructor() {
+ super('contextCommandError', {
+ emitter: 'contextMenuCommandHandler',
+ event: 'error',
+ category: 'contextCommands'
+ });
+ }
+
+ public override exec(...[error, interaction, command]: ContextMenuCommandHandlerEvents['error']) {
+ return ContextCommandErrorListener.handleError(error, interaction, command);
+ }
+
+ public static async handleError(...[error, interaction, command]: ContextMenuCommandHandlerEvents['error']) {
+ try {
+ const errorNum = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number
+ const channel = interaction.channel?.isDM()
+ ? interaction.channel.recipient?.tag
+ : (<GuildTextBasedChannel>interaction.channel)?.name;
+
+ client.sentry.captureException(error, {
+ level: Severity.Error,
+ user: { id: interaction.user.id, username: interaction.user.tag },
+ extra: {
+ 'command.name': command?.id,
+ 'message.id': interaction.id,
+ 'message.type': 'context command',
+ 'channel.id':
+ (interaction.channel?.isDM() ? interaction.channel.recipient?.id : interaction.channel?.id) ?? '¯\\_(ツ)_/¯',
+ 'channel.name': channel,
+ 'guild.id': interaction.guild?.id ?? '¯\\_(ツ)_/¯',
+ 'guild.name': interaction.guild?.name ?? '¯\\_(ツ)_/¯',
+ 'environment': client.config.environment
+ }
+ });
+
+ void client.console.error(
+ `contextCommandError`,
+ `an error occurred with the <<${command}>> context command in <<${channel}>> triggered by <<${
+ interaction?.user?.tag
+ }>>:\n${error?.stack ?? <any>error}`,
+ false
+ );
+
+ const _haste = CommandErrorListener.getErrorHaste(error);
+ const _stack = CommandErrorListener.getErrorStack(error);
+ const [haste, stack] = await Promise.all([_haste, _stack]);
+ const options = { interaction, error, errorNum, command, channel, haste, stack };
+
+ const errorEmbed = this._generateErrorEmbed({
+ ...options,
+ type: 'command-log'
+ });
+
+ void client.logger.channelError({ embeds: [errorEmbed] });
+
+ if (interaction) {
+ if (!client.config.owners.includes(interaction.user.id)) {
+ const errorUserEmbed = this._generateErrorEmbed({
+ ...options,
+ type: 'command-user'
+ });
+ void interaction?.reply({ embeds: [errorUserEmbed] }).catch(() => null);
+ } else {
+ const errorDevEmbed = this._generateErrorEmbed({
+ ...options,
+ type: 'command-dev'
+ });
+
+ void interaction?.reply({ embeds: [errorDevEmbed] }).catch(() => null);
+ }
+ }
+ } catch (e) {
+ throw new IFuckedUpError('An error occurred while handling a command error.', error, e);
+ }
+ }
+
+ private static _generateErrorEmbed(options: {
+ interaction: ContextMenuCommandInteraction;
+ error: Error | any;
+ type: 'command-log' | 'command-dev' | 'command-user';
+ errorNum: number;
+ command?: ContextMenuCommand;
+ channel?: string;
+ haste: string[];
+ stack: string;
+ }): EmbedBuilder {
+ const embed = new EmbedBuilder().setColor(util.colors.error).setTimestamp();
+ if (options.type === 'command-user') {
+ return embed
+ .setTitle('An Error Occurred')
+ .setDescription(
+ `Oh no! ${
+ options.command ? `While running the command ${util.format.input(options.command.id)}, a` : 'A'
+ }n error occurred. Please give the developers code ${util.format.input(`${options.errorNum}`)}.`
+ );
+ }
+ const description = new Array<string>();
+
+ if (options.type === 'command-log') {
+ description.push(
+ `**User:** ${options.interaction.user} (${options.interaction.user.tag})`,
+ `**Command:** ${options.command ?? 'N/A'}`,
+ `**Channel:** <#${options.interaction.channel?.id}> (${options.channel})`
+ );
+ }
+
+ description.push(...options.haste);
+
+ embed.addFields([{ name: 'Stack Trace', value: options.stack.substring(0, 1024) }]);
+ if (description.length) embed.setDescription(description.join('\n').substring(0, 4000));
+
+ if (options.type === 'command-dev' || options.type === 'command-log')
+ embed.setTitle(`ContextCommandError #${util.format.input(`${options.errorNum}`)}`);
+ return embed;
+ }
+}
diff --git a/src/listeners/contextCommands/contextCommandNotFound.ts b/src/listeners/contextCommands/contextCommandNotFound.ts
new file mode 100644
index 0000000..b0a8f62
--- /dev/null
+++ b/src/listeners/contextCommands/contextCommandNotFound.ts
@@ -0,0 +1,16 @@
+import { BushListener } from '#lib';
+import { ContextMenuCommandHandlerEvents } from 'discord-akairo';
+
+export default class ContextCommandNotFoundListener extends BushListener {
+ public constructor() {
+ super('contextCommandNotFound', {
+ emitter: 'contextMenuCommandHandler',
+ event: 'notFound',
+ category: 'contextCommands'
+ });
+ }
+
+ public override async exec(...[interaction]: ContextMenuCommandHandlerEvents['notFound']) {
+ void client.console.info('contextCommandNotFound', `<<${interaction?.commandName}>> could not be found.`);
+ }
+}
diff --git a/src/listeners/contextCommands/contextCommandStarted.ts b/src/listeners/contextCommands/contextCommandStarted.ts
new file mode 100644
index 0000000..91eceaa
--- /dev/null
+++ b/src/listeners/contextCommands/contextCommandStarted.ts
@@ -0,0 +1,27 @@
+import { BushListener } from '#lib';
+import { ContextMenuCommandHandlerEvents } from 'discord-akairo';
+import { ChannelType } from 'discord.js';
+
+export default class ContextCommandStartedListener extends BushListener {
+ public constructor() {
+ super('contextCommandStarted', {
+ emitter: 'contextMenuCommandHandler',
+ event: 'started',
+ category: 'contextCommands'
+ });
+ }
+
+ public override async exec(...[interaction, command]: ContextMenuCommandHandlerEvents['started']) {
+ return void client.logger.info(
+ 'contextCommandStarted',
+ `The <<${command.id}>> command was used by <<${interaction.user.tag}>> in ${
+ interaction.channel
+ ? interaction.channel.type === ChannelType.DM
+ ? `their <<DMs>>`
+ : `<<#${interaction.channel.name}>> in <<${interaction.guild?.name}>>`
+ : 'unknown'
+ }.`,
+ true
+ );
+ }
+}