diff options
Diffstat (limited to 'src')
90 files changed, 1473 insertions, 1272 deletions
diff --git a/src/commands/_fake-command/ironmoon.ts b/src/commands/_fake-command/ironmoon.ts index ddc6ced..d7e737f 100644 --- a/src/commands/_fake-command/ironmoon.ts +++ b/src/commands/_fake-command/ironmoon.ts @@ -5,7 +5,9 @@ export default class IronmoonCommand extends BushCommand { super('ironmoon', { category: 'fake-commands', description: { content: '', examples: '', usage: '' }, - pseudo: true + pseudo: true, + clientPermissions: [], + userPermissions: [] }); } public override condition(message: BushMessage): boolean { diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts index f313a8f..f8c97a9 100644 --- a/src/commands/admin/channelPermissions.ts +++ b/src/commands/admin/channelPermissions.ts @@ -1,5 +1,6 @@ import { GuildMember, MessageEmbed, Role } from 'discord.js'; import { BushCommand, BushMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class ChannelPermissionsCommand extends BushCommand { public constructor() { @@ -42,9 +43,7 @@ export default class ChannelPermissionsCommand extends BushCommand { } } ], - ratelimit: 4, - cooldown: 4000, - clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_CHANNELS']), userPermissions: ['ADMINISTRATOR'], channel: 'guild' }); @@ -85,7 +84,7 @@ export default class ChannelPermissionsCommand extends BushCommand { paginate.push(new MessageEmbed().setDescription(failure.substring(i, Math.min(failure.length, i + 4000)))); } const normalMessage = `Finished changing perms! Failed channels:`; - return await client.util.buttonPaginate(message, paginate, normalMessage); + return await ButtonPaginator.send(message, paginate, normalMessage); } else { return await message.util.reply({ content: `Finished changing perms! Failed channels:`, diff --git a/src/commands/admin/roleAll.ts b/src/commands/admin/roleAll.ts index d965239..ec1f2b5 100644 --- a/src/commands/admin/roleAll.ts +++ b/src/commands/admin/roleAll.ts @@ -28,7 +28,7 @@ export default class RoleAllCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['MANAGE_ROLES', 'SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']), userPermissions: ['ADMINISTRATOR'], typing: true }); diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts index 5dea36a..9f3737d 100644 --- a/src/commands/config/blacklist.ts +++ b/src/commands/config/blacklist.ts @@ -54,8 +54,8 @@ export default class BlacklistCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['MANAGE_GUILD'] }); } diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index 2075531..72ba566 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -12,6 +12,7 @@ import { MessageOptions, MessageSelectMenu, Role, + Snowflake, User } from 'discord.js'; import _ from 'lodash'; @@ -51,11 +52,7 @@ export default class SettingsCommand extends BushCommand { description: `What would you like to add to the server's ${guildSettingsObj[ setting ].name.toLowerCase()}?'`, - type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as - | 'ROLE' - | 'STRING' - | 'CHANNEL' - | 'USER', + type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as SlashArgType, required: true } ] @@ -70,11 +67,7 @@ export default class SettingsCommand extends BushCommand { description: `What would you like to remove from the server's ${guildSettingsObj[ setting ].name.toLowerCase()}?'`, - type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as - | 'ROLE' - | 'STRING' - | 'CHANNEL' - | 'USER', + type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as SlashArgType, required: true } ] @@ -96,7 +89,7 @@ export default class SettingsCommand extends BushCommand { description: `What would you like to set the server's ${guildSettingsObj[ setting ].name.toLowerCase()} to?'`, - type: guildSettingsObj[setting].type.toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL' | 'USER', + type: guildSettingsObj[setting].type.toUpperCase() as SlashArgType, required: true } ] @@ -105,13 +98,13 @@ export default class SettingsCommand extends BushCommand { }; }), channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['MANAGE_GUILD'] }); } // I make very readable code :) - override *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> { + override *args(message: BushMessage): Generator<ArgumentOptions | Flag> { const optional = message.util.parsed!.alias === 'settings'; const setting = yield { id: 'setting', @@ -126,7 +119,7 @@ export default class SettingsCommand extends BushCommand { }; const actionType = setting - ? guildSettingsObj[setting as unknown as GuildSettings]?.type.includes('-array') + ? guildSettingsObj[setting as GuildSettings]?.type.includes('-array') ? ['view', 'add', 'remove'] : ['view', 'set'] : undefined; @@ -151,13 +144,13 @@ export default class SettingsCommand extends BushCommand { const valueType = setting && action && action !== 'view' - ? (guildSettingsObj[setting as unknown as GuildSettings].type.replace('-array', '') as 'string' | 'channel' | 'role') + ? (guildSettingsObj[setting as GuildSettings].type.replace('-array', '') as 'string' | 'channel' | 'role') : undefined; const grammar = setting && action && action !== 'view' - ? (action as unknown as 'add' | 'remove' | 'set') === 'add' + ? (action as 'add' | 'remove' | 'set') === 'add' ? `to the ${setting} setting` - : (action as unknown as 'remove' | 'set') === 'remove' + : (action as 'remove' | 'set') === 'remove' ? `from the ${setting} setting` : `the ${setting} setting to` : undefined; @@ -184,8 +177,8 @@ export default class SettingsCommand extends BushCommand { args: { setting?: GuildSettings; subcommandGroup?: GuildSettings; - action?: 'view' | 'add' | 'remove' | 'set'; - subcommand?: 'view' | 'add' | 'remove' | 'set'; + action?: Action; + subcommand?: Action; value: string | Channel | Role; } ): Promise<unknown> { @@ -214,9 +207,9 @@ export default class SettingsCommand extends BushCommand { if (!value) return await message.util.reply( `${util.emojis.error} You must choose a value to ${action} ${ - (action as unknown as 'add' | 'remove' | 'set') === 'add' + action === 'add' ? `to the ${setting} setting` - : (action as unknown as 'remove' | 'set') === 'remove' + : action === 'remove' ? `from the ${setting} setting` : `the ${setting} setting to` }` @@ -295,12 +288,10 @@ export default class SettingsCommand extends BushCommand { return { embeds: [settingsEmbed], components: [selMenu] }; } else { settingsEmbed.setTitle(guildSettingsObj[setting].name); - const generateCurrentValue = async ( - type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' | 'user' | 'user-array' | 'custom' - ): Promise<string> => { + const generateCurrentValue = async (type: SettingTypes): Promise<string> => { const feat = await message.guild!.getSetting(setting); - switch (type.replace('-array', '') as 'string' | 'channel' | 'role' | 'user' | 'custom') { + switch (type.replace('-array', '') as BaseSettingTypes) { case 'string': { return Array.isArray(feat) ? feat.length @@ -315,21 +306,21 @@ export default class SettingsCommand extends BushCommand { ? feat.length ? feat.map((feat) => `<#${feat}>`).join('\n') : '[Empty Array]' - : `<#${feat as string}>`; + : `<#${feat as Snowflake}>`; } case 'role': { return Array.isArray(feat) ? feat.length ? feat.map((feat) => `<@&${feat}>`).join('\n') : '[Empty Array]' - : `<@&${feat as string}>`; + : `<@&${feat as Snowflake}>`; } case 'user': { return Array.isArray(feat) ? feat.length ? feat.map((feat) => `<@${feat}>`).join('\n') : '[Empty Array]' - : `<@${feat as string}>`; + : `<@${feat as Snowflake}>`; } case 'custom': { return util.inspectAndRedact(feat); @@ -355,13 +346,13 @@ export default class SettingsCommand extends BushCommand { guildSettingsObj[setting].type.includes('-array') ? 'add/remove' : 'set' } <value>" to set this setting.` ); - settingsEmbed.addField( - 'value', - (await generateCurrentValue( - guildSettingsObj[setting].type as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' - )) || '[No Value Set]' - ); + settingsEmbed.addField('value', (await generateCurrentValue(guildSettingsObj[setting].type)) || '[No Value Set]'); return { embeds: [settingsEmbed], components: [components] }; } } } + +type SlashArgType = 'ROLE' | 'STRING' | 'CHANNEL' | 'USER'; +type BaseSettingTypes = 'string' | 'channel' | 'role' | 'user' | 'custom'; +type SettingTypes = 'string' | 'channel' | 'channel-array' | 'role' | 'role-array' | 'user' | 'user-array' | 'custom'; +type Action = 'view' | 'add' | 'remove' | 'set'; diff --git a/src/commands/config/customAutomodPhrases.ts b/src/commands/config/customAutomodPhrases.ts index 2cbe874..51e219a 100644 --- a/src/commands/config/customAutomodPhrases.ts +++ b/src/commands/config/customAutomodPhrases.ts @@ -49,8 +49,8 @@ // ownerOnly: true, // channel: 'guild', // hidden: true, -// clientPermissions: ['SEND_MESSAGES'], -// userPermissions: ['SEND_MESSAGES'] +// clientPermissions: (m) => util.clientSendAndPermCheck(m), +// userPermissions: [] // }); // } diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts index db4909a..bca1207 100644 --- a/src/commands/config/disable.ts +++ b/src/commands/config/disable.ts @@ -52,8 +52,8 @@ export default class DisableCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['MANAGE_GUILD'] }); } @@ -63,13 +63,12 @@ export default class DisableCommand extends BushCommand { message: BushMessage | BushSlashMessage, args: { action: 'enable' | 'disable'; command: BushCommand | string; global: boolean } ): Promise<unknown> { - let action: 'disable' | 'enable' | 'toggle' = - args.action ?? (message?.util?.parsed?.alias as 'disable' | 'enable') ?? 'toggle'; + let action = (args.action ?? message?.util?.parsed?.alias ?? 'toggle') as 'disable' | 'enable' | 'toggle'; const global = args.global && message.author.isOwner(); const commandID = (args.command as BushCommand).id; if (global) { - if ((action as 'disable' | 'enable' | 'toggle') === 'toggle') { + if (action === 'toggle') { const disabledCommands = ( (await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment })) @@ -99,7 +98,7 @@ export default class DisableCommand extends BushCommand { // guild disable } else { const disabledCommands = await message.guild!.getSetting('disabledCommands'); - if ((action as 'disable' | 'enable' | 'toggle') === 'toggle') { + if (action === 'toggle') { action = disabledCommands.includes(commandID) ? 'disable' : 'enable'; } const newValue = util.addOrRemoveFromArray(action === 'disable' ? 'remove' : 'add', disabledCommands, commandID); diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts index 076d469..3c3075c 100644 --- a/src/commands/config/features.ts +++ b/src/commands/config/features.ts @@ -13,8 +13,8 @@ export default class FeaturesCommand extends BushCommand { }, slash: true, channel: 'guild', - clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: ['MANAGE_GUILD'] }); } diff --git a/src/commands/config/levelRoles.ts b/src/commands/config/levelRoles.ts index 36dc50c..ee5f255 100644 --- a/src/commands/config/levelRoles.ts +++ b/src/commands/config/levelRoles.ts @@ -1,65 +1,65 @@ -import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; +// import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -export default class LevelRolesCommand extends BushCommand { - public constructor() { - super('levelRole', { - aliases: ['level-role', 'level-roles', 'lr'], - category: 'config', - description: { - content: 'Configure roles to be assigned to users upon reaching certain levels.', - usage: ['level-role add <level> <role>', 'level-role remove <level>'], - examples: ['level-role 1 2'] - }, - args: [ - { - id: 'action', - customType: ['add', 'remove'] - }, - { - id: 'role', - type: 'role', - prompt: { - start: 'What would you like to set your first argument to be?', - retry: '{error} Pick a valid argument.', - optional: false - } - }, - { - id: 'level', - type: 'integer', - prompt: { - start: 'What would you like to set your second argument to be?', - retry: '{error} Pick a valid argument.', - optional: false - } - } - ], - slash: true, - slashOptions: [ - { - name: 'role', - description: 'What would you like to set your first argument to be?', - type: 'STRING', - required: true - }, - { - name: 'level', - description: 'What would you like to set your second argument to be?', - type: 'STRING', - required: true - } - ], - channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD', 'MANAGE_ROLES'] - }); - } +// export default class LevelRolesCommand extends BushCommand { +// public constructor() { +// super('levelRole', { +// aliases: ['level-role', 'level-roles', 'lr'], +// category: 'config', +// description: { +// content: 'Configure roles to be assigned to users upon reaching certain levels.', +// usage: ['level-role add <level> <role>', 'level-role remove <level>'], +// examples: ['level-role 1 2'] +// }, +// args: [ +// { +// id: 'action', +// customType: ['add', 'remove'] +// }, +// { +// id: 'role', +// type: 'role', +// prompt: { +// start: 'What would you like to set your first argument to be?', +// retry: '{error} Pick a valid argument.', +// optional: false +// } +// }, +// { +// id: 'level', +// type: 'integer', +// prompt: { +// start: 'What would you like to set your second argument to be?', +// retry: '{error} Pick a valid argument.', +// optional: false +// } +// } +// ], +// slash: true, +// slashOptions: [ +// { +// name: 'role', +// description: 'What would you like to set your first argument to be?', +// type: 'STRING', +// required: true +// }, +// { +// name: 'level', +// description: 'What would you like to set your second argument to be?', +// type: 'STRING', +// required: true +// } +// ], +// channel: 'guild', +// clientPermissions: (m) => util.clientSendAndPermCheck(m), +// userPermissions: ['MANAGE_GUILD', 'MANAGE_ROLES'] +// }); +// } - public override async exec( - message: BushMessage | BushSlashMessage, - args: { required_argument: string; optional_argument: string } - ): Promise<unknown> { - return await message.util.reply(`${util.emojis.error} Do not use the template command.`); - args; - } -} +// public override async exec( +// message: BushMessage | BushSlashMessage, +// args: { required_argument: string; optional_argument: string } +// ): Promise<unknown> { +// return await message.util.reply(`${util.emojis.error} Do not use the template command.`); +// args; +// } +// } diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts index 03e3582..363620a 100644 --- a/src/commands/config/log.ts +++ b/src/commands/config/log.ts @@ -29,8 +29,8 @@ export default class LogCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['MANAGE_GUILD'] }); } diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts index 49549af..b9d9114 100644 --- a/src/commands/dev/__template.ts +++ b/src/commands/dev/__template.ts @@ -49,8 +49,8 @@ export default class TemplateCommand extends BushCommand { ownerOnly: true, channel: 'guild', hidden: true, - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index 2f61822..c4a6e8f 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -45,7 +45,9 @@ export default class EvalCommand extends BushCommand { { name: 'show_proto', description: 'Show prototype.', type: 'BOOLEAN', required: false }, { name: 'show_methods', description: 'Show class functions.', type: 'BOOLEAN', required: false } ], - ownerOnly: true + ownerOnly: true, + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index 987cd01..43b8ab6 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -19,6 +19,7 @@ export default class ReloadCommand extends BushCommand { // ], ownerOnly: true, typing: true, + slash: true, // slashOptions: [ // { // name: 'fast', @@ -27,7 +28,8 @@ export default class ReloadCommand extends BushCommand { // required: false // } // ], - slash: true + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts index 1c797ea..a7e9943 100644 --- a/src/commands/dev/say.ts +++ b/src/commands/dev/say.ts @@ -13,7 +13,7 @@ export default class SayCommand extends BushCommand { }, args: [ { - id: 'say', + id: 'content', type: 'string', match: 'rest', prompt: { start: 'What would you like the bot to say?', retry: '{error} Choose something valid to say.' } @@ -21,20 +21,21 @@ export default class SayCommand extends BushCommand { ], slashOptions: [{ name: 'content', description: 'What would you like the bot to say?', type: 'STRING' }], ownerOnly: true, - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], slash: true }); } - public override async exec(message: BushMessage, { say }: { say: string }): Promise<unknown> { + public override async exec(message: BushMessage, args: { content: string }): Promise<unknown> { if (!message.author.isOwner()) return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`); await message.delete().catch(() => {}); - await message.util.send({ content: say, allowedMentions: AllowedMentions.none() }); + await message.util.send({ content: args.content, allowedMentions: AllowedMentions.none() }); } - public override async execSlash(message: AkairoMessage, { content }: { content: string }): Promise<unknown> { + public override async execSlash(message: AkairoMessage, args: { content: string }): Promise<unknown> { if (!client.config.owners.includes(message.author.id)) { return await message.interaction.reply({ content: `${util.emojis.error} Only my developers can run this command.`, @@ -42,6 +43,6 @@ export default class SayCommand extends BushCommand { }); } await message.interaction.reply({ content: 'Attempting to send message.', ephemeral: true }); - return message.channel!.send({ content, allowedMentions: AllowedMentions.none() }); + return message.channel!.send({ content: args.content, allowedMentions: AllowedMentions.none() }); } } diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts index c86e889..e366a64 100644 --- a/src/commands/dev/servers.ts +++ b/src/commands/dev/servers.ts @@ -1,5 +1,6 @@ -import { Guild, MessageEmbed } from 'discord.js'; +import { Guild, MessageEmbedOptions } from 'discord.js'; import { BushCommand, BushMessage, BushSlashMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class ServersCommand extends BushCommand { public constructor() { @@ -11,38 +12,30 @@ export default class ServersCommand extends BushCommand { usage: 'servers', examples: ['servers'] }, - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], ownerOnly: true }); } public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { - const maxLength = 10; const guilds = [...client.guilds.cache.sort((a, b) => (a.memberCount < b.memberCount ? 1 : -1)).values()]; - const chunkedGuilds: Guild[][] = []; - const embeds: MessageEmbed[] = []; - - for (let i = 0, j = guilds.length; i < j; i += maxLength) { - chunkedGuilds.push(guilds.slice(i, i + maxLength)); - } - - chunkedGuilds.forEach((c: Guild[]) => { - const embed = new MessageEmbed(); - c.forEach((g: Guild) => { - const owner = client.users.cache.get(g.ownerId)?.tag; - embed - .addField( - `**${g.name}**`, - `**ID:** ${g.id}\n**Owner:** ${owner ? owner : g.ownerId}\n**Members:** ${g.memberCount.toLocaleString()}`, - false - ) - .setTitle(`Server List [\`${client.guilds.cache.size.toLocaleString()}\`]`) - .setColor(util.colors.default); - }); - embeds.push(embed); + const chunkedGuilds: Guild[][] = util.chunk(guilds, 10); + const embeds: MessageEmbedOptions[] = chunkedGuilds.map((chunk) => { + return { + title: `Server List [\`${guilds.length.toLocaleString()}\`]`, + color: util.colors.default, + fields: chunk.map((guild) => ({ + name: util.format.bold(guild.name), + value: [ + `**ID:** ${guild.id}`, + `**Owner:** ${client.users.cache.has(guild.ownerId) ? client.users.cache.get(guild.ownerId)!.tag : guild.ownerId}`, + `**Members:** ${guild.memberCount.toLocaleString()}` + ].join('\n') + })) + } as MessageEmbedOptions; }); - return await util.buttonPaginate(message, embeds); + return await ButtonPaginator.send(message, embeds); } } diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts index 067a0e6..3fca2b2 100644 --- a/src/commands/dev/sh.ts +++ b/src/commands/dev/sh.ts @@ -33,7 +33,9 @@ export default class ShCommand extends BushCommand { } } ], - ownerOnly: true + ownerOnly: true, + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts index 4dc4584..fcdec53 100644 --- a/src/commands/dev/superUser.ts +++ b/src/commands/dev/superUser.ts @@ -12,7 +12,8 @@ export default class SuperUserCommand extends BushCommand { usage: 'superuser <add/remove> <user>', examples: ['superuser add IRONM00N'] }, - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], ownerOnly: true }); } diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts index c77d7ec..a1e5052 100644 --- a/src/commands/dev/test.ts +++ b/src/commands/dev/test.ts @@ -7,6 +7,7 @@ import { MessageButton, MessageEmbed } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class TestCommand extends BushCommand { public constructor() { @@ -18,8 +19,6 @@ export default class TestCommand extends BushCommand { usage: 'test [feature]', examples: ['test lots of buttons', 'test buttons'] }, - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], args: [ { id: 'feature', @@ -32,7 +31,9 @@ export default class TestCommand extends BushCommand { } } ], - superUserOnly: true + superUserOnly: true, + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } @@ -95,7 +96,7 @@ export default class TestCommand extends BushCommand { for (let i = 1; i <= 5; i++) { embeds.push(new MessageEmbed().setDescription(i.toString())); } - return await util.buttonPaginate(message, embeds); + return await ButtonPaginator.send(message, embeds); } else if (['lots of embeds'].includes(args?.feature?.toLowerCase())) { const description = 'This is a description.'; const _avatar = message.author.avatarURL({ dynamic: true }) ?? undefined; diff --git a/src/commands/fun/coinflip.ts b/src/commands/fun/coinflip.ts index e0892b7..42e7167 100644 --- a/src/commands/fun/coinflip.ts +++ b/src/commands/fun/coinflip.ts @@ -10,14 +10,15 @@ export default class CoinFlipCommand extends BushCommand { usage: 'coinflip', examples: ['coinflip'] }, - clientPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } public override async exec(message: BushMessage | BushSlashMessage): Promise<void> { const random = Math.random(); let result: string; - const fall = message.author.id === '322862723090219008' ? 0.1 : 0.001; + const fall = message.author.id === '322862723090219008' ? 0.1 : 0.001; //dw about it if (random < fall) result = 'The coin fell off the table :('; else if (random <= 0.5 + fall / 2) result = 'Heads'; else result = 'Tails'; diff --git a/src/commands/fun/dice.ts b/src/commands/fun/dice.ts index 241f1d2..9f18657 100644 --- a/src/commands/fun/dice.ts +++ b/src/commands/fun/dice.ts @@ -10,7 +10,8 @@ export default class EightBallCommand extends BushCommand { usage: 'dice', examples: ['dice'] }, - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], slash: true }); } diff --git a/src/commands/fun/eightBall.ts b/src/commands/fun/eightBall.ts index efaaff5..b3c56da 100644 --- a/src/commands/fun/eightBall.ts +++ b/src/commands/fun/eightBall.ts @@ -30,8 +30,8 @@ export default class EightBallCommand extends BushCommand { required: true } ], - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/fun/minesweeper.ts b/src/commands/fun/minesweeper.ts index 57909a2..b45dcda 100644 --- a/src/commands/fun/minesweeper.ts +++ b/src/commands/fun/minesweeper.ts @@ -42,43 +42,15 @@ export default class MinesweeperCommand extends BushCommand { }, default: 10 }, - { - id: 'spaces', - match: 'flag', - flag: '--spaces' - }, - { - id: 'reveal_first_cell', - match: 'flag', - flag: '--revealFirstCell' - } + { id: 'spaces', match: 'flag', flag: '--spaces' }, + { id: 'reveal_first_cell', match: 'flag', flag: '--revealFirstCell' } ], slash: true, slashOptions: [ - { - name: 'rows', - description: 'How many rows would you like?', - type: 'INTEGER', - required: false - }, - { - name: 'columns', - description: 'How many rows would you like?', - type: 'INTEGER', - required: false - }, - { - name: 'mines', - description: 'How many rows would you like?', - type: 'INTEGER', - required: false - }, - { - name: 'spaces', - description: 'Would you like there to be spaces?', - type: 'BOOLEAN', - required: false - }, + { name: 'rows', description: 'How many rows would you like?', type: 'INTEGER', required: false }, + { name: 'columns', description: 'How many rows would you like?', type: 'INTEGER', required: false }, + { name: 'mines', description: 'How many rows would you like?', type: 'INTEGER', required: false }, + { name: 'spaces', description: 'Would you like there to be spaces?', type: 'BOOLEAN', required: false }, { name: 'reveal_first_cell', description: 'Would you like to automatically reveal the first cell?', @@ -86,35 +58,23 @@ export default class MinesweeperCommand extends BushCommand { required: false } ], - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } public override async exec( message: BushMessage | BushSlashMessage, - { - rows, - columns, - mines, - spaces, - reveal_first_cell - }: { - rows: number; - columns: number; - mines: number; - spaces: boolean; - reveal_first_cell: boolean; - } + args: { rows: number; columns: number; mines: number; spaces: boolean; reveal_first_cell: boolean } ): Promise<unknown> { const minesweeper = new Minesweeper({ - rows: rows ?? 9, - columns: columns ?? 9, - mines: mines ?? 10, + rows: args.rows ?? 9, + columns: args.columns ?? 9, + mines: args.mines ?? 10, emote: 'boom', - revealFirstCell: reveal_first_cell ?? false, + revealFirstCell: args.reveal_first_cell ?? false, zeroFirstCell: true, - spaces: spaces ?? false, + spaces: args.spaces ?? false, returnType: 'emoji' }); const matrix = minesweeper.start(); diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts index 1e6496a..7625b61 100644 --- a/src/commands/info/avatar.ts +++ b/src/commands/info/avatar.ts @@ -22,7 +22,8 @@ export default class AvatarCommand extends BushCommand { } } ], - clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], slash: true, slashOptions: [ { diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts index 257dc90..ffb785c 100644 --- a/src/commands/info/botInfo.ts +++ b/src/commands/info/botInfo.ts @@ -14,8 +14,8 @@ export default class BotInfoCommand extends BushCommand { examples: ['botinfo'] }, slash: true, - clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/info/color.ts b/src/commands/info/color.ts index d641fd7..958aadd 100644 --- a/src/commands/info/color.ts +++ b/src/commands/info/color.ts @@ -31,7 +31,8 @@ export default class ColorCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts index f1db783..4a79918 100644 --- a/src/commands/info/guildInfo.ts +++ b/src/commands/info/guildInfo.ts @@ -31,8 +31,8 @@ export default class GuildInfoCommand extends BushCommand { required: false } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts index ce171c9..5e80199 100644 --- a/src/commands/info/help.ts +++ b/src/commands/info/help.ts @@ -37,8 +37,8 @@ export default class HelpCommand extends BushCommand { required: false } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/info/icon.ts b/src/commands/info/icon.ts index 677fdaf..ce27cc0 100644 --- a/src/commands/info/icon.ts +++ b/src/commands/info/icon.ts @@ -11,7 +11,8 @@ export default class IconCommand extends BushCommand { usage: 'icon', examples: 'icon' }, - clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], channel: 'guild', slash: true }); diff --git a/src/commands/info/links.ts b/src/commands/info/links.ts index b3a762a..569b959 100644 --- a/src/commands/info/links.ts +++ b/src/commands/info/links.ts @@ -14,7 +14,8 @@ export default class LinksCommand extends BushCommand { }, ratelimit: 4, cooldown: 4000, - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], slash: true }); } diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts index 5f2220c..82db2ff 100644 --- a/src/commands/info/ping.ts +++ b/src/commands/info/ping.ts @@ -12,8 +12,8 @@ export default class PingCommand extends BushCommand { examples: ['ping'] }, slash: true, - clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts index 77612da..cd66530 100644 --- a/src/commands/info/pronouns.ts +++ b/src/commands/info/pronouns.ts @@ -1,5 +1,4 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { Snowflake } from 'discord-api-types'; import { MessageEmbed, User } from 'discord.js'; export default class PronounsCommand extends BushCommand { @@ -15,7 +14,7 @@ export default class PronounsCommand extends BushCommand { args: [ { id: 'user', - customType: util.arg.union('user', 'snowflake'), + type: 'globalUser', prompt: { start: 'Who would you like to view the pronouns of?', retry: '{error} Choose a valid user to view the pronouns of.', @@ -23,7 +22,8 @@ export default class PronounsCommand extends BushCommand { } } ], - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], slashOptions: [ { name: 'user', @@ -35,18 +35,15 @@ export default class PronounsCommand extends BushCommand { slash: true }); } - override async exec(message: BushMessage | BushSlashMessage, args: { user?: User | Snowflake }): Promise<unknown> { - const user = (await util.resolveNonCachedUser(args.user)) ?? message.author; - - if (!user) return message.util.reply(`${util.emojis.error} Invalid user.`); - + override async exec(message: BushMessage | BushSlashMessage, args: { user?: User }): Promise<unknown> { + const user = args.user ?? message.author; const author = user.id === message.author.id; const pronouns = await util.getPronounsOf(user); if (!pronouns) { return await message.util.reply( - `${author ? 'You do' : `${user.tag} does`} not appear to have any pronouns set. Please ${ - author ? '' : 'tell them to' + `${author ? 'You do' : `${user.tag} does`} not appear to have any pronouns set. Please${ + author ? '' : ' tell them to' } go to https://pronoundb.org/ and set ${author ? 'your' : 'their'} pronouns.` ); } else { diff --git a/src/commands/info/snowflake.ts b/src/commands/info/snowflake.ts index df11bce..81b3f9d 100644 --- a/src/commands/info/snowflake.ts +++ b/src/commands/info/snowflake.ts @@ -40,7 +40,8 @@ export default class SnowflakeCommand extends BushCommand { } } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], slash: true, slashOptions: [ { diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index b55bbcf..353d844 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -33,8 +33,8 @@ export default class UserInfoCommand extends BushCommand { required: false } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts index 432fc60..e3344a0 100644 --- a/src/commands/leveling/leaderboard.ts +++ b/src/commands/leveling/leaderboard.ts @@ -1,5 +1,6 @@ import { BushCommand, BushMessage, BushSlashMessage, Level } from '@lib'; import { MessageEmbed } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class LeaderboardCommand extends BushCommand { public constructor() { @@ -32,8 +33,8 @@ export default class LeaderboardCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } @@ -62,6 +63,6 @@ export default class LeaderboardCommand extends BushCommand { const embeds = chunked.map((c) => new MessageEmbed().setTitle(`${message.guild!.name}'s Leaderboard`).setDescription(c.join('\n')) ); - return await util.buttonPaginate(message, embeds, undefined, true, args?.page ?? undefined); + return await ButtonPaginator.send(message, embeds, undefined, true, args?.page ?? undefined); } } diff --git a/src/commands/leveling/level.ts b/src/commands/leveling/level.ts index 2073e1a..4d7adb3 100644 --- a/src/commands/leveling/level.ts +++ b/src/commands/leveling/level.ts @@ -45,11 +45,42 @@ export default class LevelCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } + public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> { + if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); + if (!(await message.guild.hasFeature('leveling'))) + return await message.util.reply( + `${util.emojis.error} This command can only be run in servers with the leveling feature enabled.${ + message.member?.permissions.has('MANAGE_GUILD') + ? ` You can toggle features using the \`${ + message.util.isSlash + ? '/' + : client.config.isDevelopment + ? 'dev ' + : message.util.parsed?.prefix ?? client.config.prefix + }features\` command.` + : '' + }` + ); + const user = args.user ?? message.author; + try { + return await message.util.reply({ + files: [new MessageAttachment(await this.getImage(user, message.guild!), 'level.png')] + }); + } catch (e) { + if (e instanceof Error && e.message === 'User does not have a level') { + return await message.util.reply({ + content: `${util.emojis.error} ${user} does not have a level.`, + allowedMentions: AllowedMentions.none() + }); + } else throw e; + } + } + private async getImage(user: BushUser, guild: BushGuild): Promise<Buffer> { const guildRows = await Level.findAll({ where: { guild: guild.id } }); const rank = guildRows.sort((a, b) => b.xp - a.xp); @@ -111,35 +142,4 @@ export default class LevelCommand extends BushCommand { // Return image in buffer form return levelCard.toBuffer(); } - - public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> { - if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); - if (!(await message.guild.hasFeature('leveling'))) - return await message.util.reply( - `${util.emojis.error} This command can only be run in servers with the leveling feature enabled.${ - message.member?.permissions.has('MANAGE_GUILD') - ? ` You can toggle features using the \`${ - message.util.isSlash - ? '/' - : client.config.isDevelopment - ? 'dev ' - : message.util.parsed?.prefix ?? client.config.prefix - }features\` command.` - : '' - }` - ); - const user = args.user ?? message.author; - try { - return await message.util.reply({ - files: [new MessageAttachment(await this.getImage(user, message.guild!), 'level.png')] - }); - } catch (e) { - if (e instanceof Error && e.message === 'User does not have a level') { - return await message.util.reply({ - content: `${util.emojis.error} ${user} does not have a level.`, - allowedMentions: AllowedMentions.none() - }); - } else throw e; - } - } } diff --git a/src/commands/leveling/setLevel.ts b/src/commands/leveling/setLevel.ts index b98b488..9a7337a 100644 --- a/src/commands/leveling/setLevel.ts +++ b/src/commands/leveling/setLevel.ts @@ -45,8 +45,8 @@ export default class SetLevelCommand extends BushCommand { ], slash: true, channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'ADMINISTRATOR'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['ADMINISTRATOR'] }); } diff --git a/src/commands/leveling/setXp.ts b/src/commands/leveling/setXp.ts index 3e00ea2..a73ae58 100644 --- a/src/commands/leveling/setXp.ts +++ b/src/commands/leveling/setXp.ts @@ -48,8 +48,8 @@ export default class SetXpCommand extends BushCommand { ], slash: true, channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'ADMINISTRATOR'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: ['ADMINISTRATOR'] }); } diff --git a/src/commands/moderation/_lockdown.ts b/src/commands/moderation/_lockdown.ts index 5df9f18..0086ff6 100644 --- a/src/commands/moderation/_lockdown.ts +++ b/src/commands/moderation/_lockdown.ts @@ -27,15 +27,15 @@ export default class LockdownCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], hidden: true }); } - public override async exec(message: BushMessage | BushSlashMessage, { all }: { all: boolean }): Promise<unknown> { + public override async exec(message: BushMessage | BushSlashMessage, args: { all: boolean }): Promise<unknown> { return await message.util.reply('Unfortunately my developer is too lazy to implement this command.'); - if (!all) { + if (!args.all) { if (!['GUILD_TEXT', 'GUILD_NEWS'].includes(message.channel!.type)) return message.util.reply(`${util.emojis.error} You can only lock down text and announcement channels.`); diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 4c2b3d3..b3d97d2 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -1,6 +1,6 @@ import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { Snowflake, User } from 'discord.js'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation'; export default class BanCommand extends BushCommand { public constructor() { diff --git a/src/commands/moderation/evidence.ts b/src/commands/moderation/evidence.ts index 250df24..a681bc1 100644 --- a/src/commands/moderation/evidence.ts +++ b/src/commands/moderation/evidence.ts @@ -27,8 +27,8 @@ export default class EvidenceCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']) }); } diff --git a/src/commands/moderation/hideCase.ts b/src/commands/moderation/hideCase.ts index 2ed788a..38cfe31 100644 --- a/src/commands/moderation/hideCase.ts +++ b/src/commands/moderation/hideCase.ts @@ -20,10 +20,8 @@ export default class HideCaseCommand extends BushCommand { } } ], - userPermissions: (message) => { - return message.member?.permissions.has('MANAGE_MESSAGES') ? null : ['MANAGE_MESSAGES']; - }, - clientPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']), slash: true, slashOptions: [ { diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 715483a..9bd5658 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -1,5 +1,5 @@ -import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; +import { Moderation } from '../../lib/common/Moderation'; export default class KickCommand extends BushCommand { public constructor() { @@ -51,7 +51,7 @@ export default class KickCommand extends BushCommand { required: false } ], - clientPermissions: ['SEND_MESSAGES', 'KICK_MEMBERS'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['KICK_MEMBERS']), userPermissions: ['KICK_MEMBERS'] }); } @@ -60,7 +60,7 @@ export default class KickCommand extends BushCommand { message: BushMessage | BushSlashMessage, { user, reason, force }: { user: BushUser; reason?: string; force: boolean } ): Promise<unknown> { - const member = message.guild!.members.cache.get(user.id) as BushGuildMember; + const member = await message.guild!.members.fetch(user.id); if (!member) return await message.util.reply( diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts index d5c6f91..eb37681 100644 --- a/src/commands/moderation/modlog.ts +++ b/src/commands/moderation/modlog.ts @@ -1,5 +1,6 @@ import { BushCommand, BushMessage, BushSlashMessage, BushUser, ModLog } from '@lib'; import { MessageEmbed, User } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class ModlogCommand extends BushCommand { public constructor() { @@ -27,7 +28,8 @@ export default class ModlogCommand extends BushCommand { default: false } ], - userPermissions: [], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']), slash: true, slashOptions: [ { @@ -62,10 +64,6 @@ export default class ModlogCommand extends BushCommand { message: BushMessage | BushSlashMessage, { search, hidden }: { search: BushUser | string; hidden: boolean } ): Promise<unknown> { - if (!message.member?.permissions.has('MANAGE_MESSAGES')) - return await message.util.reply( - `${util.emojis.error} You must have the **Manage Message** permission to use this command.` - ); const foundUser = search instanceof User ? search : await util.resolveUserAsync(search); if (foundUser) { const logs = await ModLog.findAll({ @@ -90,7 +88,7 @@ export default class ModlogCommand extends BushCommand { color: util.colors.default }) ); - return await util.buttonPaginate(message, embedPages, undefined, true); + return await ButtonPaginator.send(message, embedPages, undefined, true); } else if (search) { const entry = await ModLog.findByPk(search as string); if (!entry || entry.pseudo || (entry.hidden && !hidden)) @@ -102,7 +100,7 @@ export default class ModlogCommand extends BushCommand { description: this.#generateModlogInfo(entry, true), color: util.colors.default }; - return await util.buttonPaginate(message, [embed]); + return await ButtonPaginator.send(message, [embed]); } } } diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 942c0b0..03ecf2a 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -1,5 +1,5 @@ import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation'; export default class MuteCommand extends BushCommand { public constructor() { @@ -52,8 +52,8 @@ export default class MuteCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'], - userPermissions: ['MANAGE_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']) }); } @@ -66,7 +66,7 @@ export default class MuteCommand extends BushCommand { }: { user: BushUser; reason?: { duration: number | null; contentWithoutTime: string }; force: boolean } ): Promise<unknown> { if (reason?.duration === null) reason.duration = 0; - const member = message.guild!.members.cache.get(user.id); + const member = await message.guild!.members.fetch(user.id).catch(() => null); if (!member) return await message.util.reply( `${util.emojis.error} The user you selected is not in the server or is not a valid user.` diff --git a/src/commands/moderation/purge.ts b/src/commands/moderation/purge.ts index 062dad0..a77e46a 100644 --- a/src/commands/moderation/purge.ts +++ b/src/commands/moderation/purge.ts @@ -37,7 +37,7 @@ export default class PurgeCommand extends BushCommand { required: false } ], - clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES', 'EMBED_LINKS'], true), userPermissions: ['MANAGE_MESSAGES'], channel: 'guild' }); diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts index dc05883..63e20bd 100644 --- a/src/commands/moderation/removeReactionEmoji.ts +++ b/src/commands/moderation/removeReactionEmoji.ts @@ -11,8 +11,6 @@ export default class RemoveReactionEmojiCommand extends BushCommand { usage: 'remove-reaction-emoji <message> <emoji>', examples: ['remove-reaction-emoji 791413052347252786 <:omegaclown:782630946435366942>'] }, - clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'], // Can't undo the removal of 1000s of reactions args: [ { id: 'messageToRemoveFrom', @@ -32,7 +30,9 @@ export default class RemoveReactionEmojiCommand extends BushCommand { } } ], - channel: 'guild' + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES', 'EMBED_LINKS'], true), + userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'] // Can't undo the removal of 1000s of reactions }); } diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts index c39864b..fe34d75 100644 --- a/src/commands/moderation/role.ts +++ b/src/commands/moderation/role.ts @@ -50,8 +50,8 @@ export default class RoleCommand extends BushCommand { ], channel: 'guild', typing: true, - clientPermissions: ['MANAGE_ROLES', 'EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES', 'EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts index 94e40ca..d90f122 100644 --- a/src/commands/moderation/slowmode.ts +++ b/src/commands/moderation/slowmode.ts @@ -42,8 +42,8 @@ export default class SlowModeCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['MANAGE_CHANNELS', 'SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_CHANNELS', 'EMBED_LINKS'], true), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']) }); } diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index a319ff9..61e27cf 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -1,5 +1,4 @@ import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { User } from 'discord.js'; export default class UnbanCommand extends BushCommand { public constructor() { @@ -14,7 +13,7 @@ export default class UnbanCommand extends BushCommand { args: [ { id: 'user', - type: 'user', + type: 'globalUser', prompt: { start: 'What user would you like to unban?', retry: '{error} Choose a valid user to unban.' @@ -55,10 +54,6 @@ export default class UnbanCommand extends BushCommand { message: BushMessage | BushSlashMessage, { user, reason }: { user: BushUser; reason?: string } ): Promise<unknown> { - if (!(user instanceof User)) { - user = util.resolveUser(user, client.users.cache) as BushUser; - } - const responseCode = await message.guild!.bushUnban({ user, moderator: message.author, diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index 3d592b7..e430b83 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -1,5 +1,5 @@ import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation'; export default class UnmuteCommand extends BushCommand { public constructor() { @@ -52,8 +52,8 @@ export default class UnmuteCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES', 'MANAGE_ROLES'], - userPermissions: ['MANAGE_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_ROLES']), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']) }); } diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 3df4b3b..6ae8442 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,5 +1,5 @@ import { BushCommand, BushGuildMember, BushMessage, BushSlashMessage, BushUser } from '@lib'; -import { Moderation } from '../../lib/common/moderation'; +import { Moderation } from '../../lib/common/Moderation'; export default class WarnCommand extends BushCommand { public constructor() { @@ -51,8 +51,8 @@ export default class WarnCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['MANAGE_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: (m) => util.userGuildPermCheck(m, ['MANAGE_MESSAGES']) }); } diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts index 978d8e5..e26a5a4 100644 --- a/src/commands/moulberry-bush/capePerms.ts +++ b/src/commands/moulberry-bush/capePerms.ts @@ -23,7 +23,8 @@ export default class CapePermissionsCommand extends BushCommand { } } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], channel: 'guild', slash: true, slashOptions: [ @@ -38,7 +39,7 @@ export default class CapePermissionsCommand extends BushCommand { } public override async exec(message: BushMessage | BushSlashMessage, args: { ign: string }): Promise<unknown> { - interface Capeperms { + interface CapePerms { success: boolean; perms: User[]; } @@ -48,7 +49,7 @@ export default class CapePermissionsCommand extends BushCommand { perms: string[]; } - let capeperms: Capeperms | null, uuid: string; + let capePerms: CapePerms | null, uuid: string; try { uuid = await util.findUUID(args.ign); } catch (e) { @@ -56,18 +57,18 @@ export default class CapePermissionsCommand extends BushCommand { } try { - capeperms = await got.get('http://moulberry.codes/permscapes.json').json(); + capePerms = await got.get('http://moulberry.codes/permscapes.json').json(); } catch (error) { - capeperms = null; + capePerms = null; } - if (capeperms == null) { + if (capePerms == null) { return await message.util.reply(`${util.emojis.error} There was an error finding cape perms for \`${args.ign}\`.`); } else { - if (capeperms?.perms) { + if (capePerms?.perms) { let index = null; - for (let i = 0; i < capeperms.perms.length; i++) { - if (capeperms.perms[i]._id == uuid) { + for (let i = 0; i < capePerms.perms.length; i++) { + if (capePerms.perms[i]._id == uuid) { index = i; break; } @@ -75,7 +76,7 @@ export default class CapePermissionsCommand extends BushCommand { } if (index == null) return await message.util.reply(`${util.emojis.error} \`${args.ign}\` does not appear to have any capes.`); - const userPerm: string[] = capeperms.perms[index].perms; + const userPerm: string[] = capePerms.perms[index].perms; const embed = new MessageEmbed() .setTitle(`${args.ign}'s Capes`) .setDescription(userPerm.join('\n')) diff --git a/src/commands/moulberry-bush/capes.ts b/src/commands/moulberry-bush/capes.ts index 14a972e..1a09eb0 100644 --- a/src/commands/moulberry-bush/capes.ts +++ b/src/commands/moulberry-bush/capes.ts @@ -1,6 +1,8 @@ -import { MessageEmbed } from 'discord.js'; +import { MessageEmbedOptions } from 'discord.js'; import got from 'got'; import { BushCommand, BushMessage } from '../../lib'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; +import { DeleteButton } from '../../lib/common/DeleteButton'; export interface GithubFile { path: string; @@ -58,8 +60,8 @@ export default class CapesCommand extends BushCommand { required: false } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } @@ -102,30 +104,33 @@ export default class CapesCommand extends BushCommand { } return 0; }); + if (args.cape) { - const capeObj = sortedCapes.find((s_cape) => s_cape.name === args.cape); - if (capeObj) { - const embed = new MessageEmbed({ - title: `${capeObj.name} cape`, - color: util.colors.default - }).setTimestamp(); - embed.setImage(capeObj.url); - await util.sendWithDeleteButton(message, { embeds: [embed] }); + const cape = sortedCapes.find((s_cape) => s_cape.name === args.cape); + if (cape) { + const embed = this.makeEmbed(cape); + await DeleteButton.send(message, { embeds: [embed] }); } else { await message.util.reply(`${util.emojis.error} Cannot find a cape called \`${args.cape}\`.`); } } else { - const embeds = []; - for (const capeObj of sortedCapes) { - const embed = new MessageEmbed({ - title: `${capeObj.name} cape`, - color: util.colors.default - }).setTimestamp(); - embed.setImage(capeObj.url); - if (capeObj.purchasable) embed.setDescription(':money_with_wings: **purchasable** :money_with_wings:'); - embeds.push(embed); - } - await util.buttonPaginate(message, embeds, null); + const embeds: MessageEmbedOptions[] = sortedCapes.map(this.makeEmbed); + await ButtonPaginator.send(message, embeds, null); } } + + private makeEmbed(cape: { + name: string; + url: string; + index: number; + purchasable?: boolean | undefined; + }): MessageEmbedOptions { + return { + title: `${cape.name} cape`, + color: util.colors.default, + timestamp: Date.now(), + image: { url: cape.url }, + description: cape.purchasable ? ':money_with_wings: **purchasable** :money_with_wings:' : undefined + }; + } } diff --git a/src/commands/moulberry-bush/giveawayPing.ts b/src/commands/moulberry-bush/giveawayPing.ts index 18f4acf..d6005b0 100644 --- a/src/commands/moulberry-bush/giveawayPing.ts +++ b/src/commands/moulberry-bush/giveawayPing.ts @@ -10,8 +10,8 @@ export default class GiveawayPingCommand extends BushCommand { usage: 'giveaway-ping', examples: ['giveaway-ping'] }, - clientPermissions: ['MANAGE_MESSAGES'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD', 'MANAGE_MESSAGES', 'BAN_MEMBERS', 'KICK_MEMBERS', 'VIEW_CHANNEL'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_MESSAGES'], true), + userPermissions: ['MANAGE_GUILD', 'MANAGE_MESSAGES', 'BAN_MEMBERS', 'KICK_MEMBERS', 'VIEW_CHANNEL'], channel: 'guild', ignoreCooldown: [], ignorePermissions: [], diff --git a/src/commands/moulberry-bush/moulHammer.ts b/src/commands/moulberry-bush/moulHammer.ts index d5930b5..129eb84 100644 --- a/src/commands/moulberry-bush/moulHammer.ts +++ b/src/commands/moulberry-bush/moulHammer.ts @@ -11,8 +11,6 @@ export default class MoulHammerCommand extends BushCommand { usage: 'moulHammer <user>', examples: ['moulHammer @IRONM00N'] }, - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], args: [ { id: 'user', @@ -23,7 +21,9 @@ export default class MoulHammerCommand extends BushCommand { } } ], - restrictedGuilds: ['516977525906341928'] + restrictedGuilds: ['516977525906341928'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts index a5c4cb2..13976bb 100644 --- a/src/commands/moulberry-bush/report.ts +++ b/src/commands/moulberry-bush/report.ts @@ -33,8 +33,6 @@ export default class ReportCommand extends BushCommand { } } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - channel: 'guild', slash: true, slashOptions: [ { @@ -49,7 +47,10 @@ export default class ReportCommand extends BushCommand { type: 'STRING', required: false } - ] + ], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], + channel: 'guild' }); } @@ -102,7 +103,6 @@ export default class ReportCommand extends BushCommand { true ); - //reusing code pog if (message.attachments.size > 0) { const fileName = message.attachments.first()!.name!.toLowerCase(); if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.gif') || fileName.endsWith('.webp')) { diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts index 0c1e435..59c9e43 100644 --- a/src/commands/moulberry-bush/rule.ts +++ b/src/commands/moulberry-bush/rule.ts @@ -81,9 +81,6 @@ export default class RuleCommand extends BushCommand { } } ], - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], - channel: 'guild', - restrictedGuilds: ['516977525906341928'], slash: true, slashOptions: [ { @@ -99,7 +96,11 @@ export default class RuleCommand extends BushCommand { required: false } ], - slashGuilds: ['516977525906341928'] + slashGuilds: ['516977525906341928'], + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], + restrictedGuilds: ['516977525906341928'] }); } diff --git a/src/commands/moulberry-bush/serverStatus.ts b/src/commands/moulberry-bush/serverStatus.ts index 8ff9803..f4a80d4 100644 --- a/src/commands/moulberry-bush/serverStatus.ts +++ b/src/commands/moulberry-bush/serverStatus.ts @@ -14,7 +14,8 @@ export default class ServerStatusCommand extends BushCommand { }, ratelimit: 4, cooldown: 4000, - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], slash: true }); } diff --git a/src/commands/utilities/activity.ts b/src/commands/utilities/activity.ts index de7e79f..c3839e5 100644 --- a/src/commands/utilities/activity.ts +++ b/src/commands/utilities/activity.ts @@ -100,8 +100,8 @@ export default class YouTubeCommand extends BushCommand { choices: Object.keys(activityMap).map((key) => ({ name: key, value: activityMap[key as keyof typeof activityMap] })) } ], - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/utilities/calculator.ts b/src/commands/utilities/calculator.ts index a2c91e4..743e9b2 100644 --- a/src/commands/utilities/calculator.ts +++ b/src/commands/utilities/calculator.ts @@ -33,8 +33,8 @@ export default class CalculatorCommand extends BushCommand { required: true } ], - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } public override async exec(message: BushMessage | BushSlashMessage, args: { expression: string }): Promise<unknown> { diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts index e48c644..c497183 100644 --- a/src/commands/utilities/decode.ts +++ b/src/commands/utilities/decode.ts @@ -3,7 +3,7 @@ import { AkairoMessage } from 'discord-akairo'; import { MessageEmbed } from 'discord.js'; const encodingTypesArray = ['ascii', 'utf8', 'utf-8', 'utf16le', 'ucs2', 'ucs-2', 'base64', 'latin1', 'binary', 'hex']; -const encodingTypesString = '`ascii`, `utf8`, `utf-8`, `utf16le`, `ucs2`, `ucs-2`, `base64`, `latin1`, `binary`, `hex`'; +const encodingTypesString = encodingTypesArray.map((e) => `\`${e}\``).join(', '); export default class DecodeCommand extends BushCommand { public constructor() { @@ -42,43 +42,20 @@ export default class DecodeCommand extends BushCommand { } } ], - clientPermissions: ['SEND_MESSAGES'], slash: true, slashOptions: [ { name: 'from', description: 'The type of data you are inputting.', type: 'STRING', - choices: [ - { name: 'ascii', value: 'ascii' }, - { name: 'utf8', value: 'utf8' }, - { name: 'utf-8', value: 'utf-8' }, - { name: 'utf16le', value: 'utf16le' }, - { name: 'ucs2', value: 'ucs2' }, - { name: 'ucs-2', value: 'ucs-2' }, - { name: 'base64', value: 'base64' }, - { name: 'latin1', value: 'latin1' }, - { name: 'binary', value: 'binary' }, - { name: 'hex', value: 'hex' } - ], + choices: encodingTypesArray.map((e) => ({ name: e, value: e })), required: true }, { name: 'to', description: 'The type of data you want the output to be.', type: 'STRING', - choices: [ - { name: 'ascii', value: 'ascii' }, - { name: 'utf8', value: 'utf8' }, - { name: 'utf-8', value: 'utf-8' }, - { name: 'utf16le', value: 'utf16le' }, - { name: 'ucs2', value: 'ucs2' }, - { name: 'ucs-2', value: 'ucs-2' }, - { name: 'base64', value: 'base64' }, - { name: 'latin1', value: 'latin1' }, - { name: 'binary', value: 'binary' }, - { name: 'hex', value: 'hex' } - ], + choices: encodingTypesArray.map((e) => ({ name: e, value: e })), required: true }, { @@ -87,7 +64,9 @@ export default class DecodeCommand extends BushCommand { type: 'STRING', required: true } - ] + ], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/utilities/hash.ts b/src/commands/utilities/hash.ts index 16ace93..df604c8 100644 --- a/src/commands/utilities/hash.ts +++ b/src/commands/utilities/hash.ts @@ -22,7 +22,8 @@ export default class HashCommand extends BushCommand { } } ], - clientPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index 497935a..a012501 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -51,15 +51,11 @@ export default class PriceCommand extends BushCommand { super('price', { aliases: ['price'], category: 'utilities', - clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], description: { usage: 'price <item id>', examples: ['price ASPECT_OF_THE_END'], content: 'Finds the price information of an item.' }, - ratelimit: 4, - cooldown: 4000, - typing: true, args: [ { id: 'item', @@ -91,7 +87,12 @@ export default class PriceCommand extends BushCommand { type: 'BOOLEAN', required: false } - ] + ], + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [], + ratelimit: 4, + cooldown: 4000, + typing: true }); } diff --git a/src/commands/utilities/steal.ts b/src/commands/utilities/steal.ts index 7d61016..767534e 100644 --- a/src/commands/utilities/steal.ts +++ b/src/commands/utilities/steal.ts @@ -27,8 +27,8 @@ export default class StealCommand extends BushCommand { ], slash: false, channel: 'guild', - clientPermissions: ['SEND_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'], - userPermissions: ['SEND_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'] + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MANAGE_EMOJIS_AND_STICKERS']), + userPermissions: ['MANAGE_EMOJIS_AND_STICKERS'] }); } public override async exec( diff --git a/src/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts index 58119ef..9289b1c 100644 --- a/src/commands/utilities/suicide.ts +++ b/src/commands/utilities/suicide.ts @@ -12,8 +12,8 @@ export default class TemplateCommand extends BushCommand { examples: ['suicide'] }, slash: true, - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], bypassChannelBlacklist: true }); } @@ -29,16 +29,20 @@ export default class TemplateCommand extends BushCommand { ) .addField( '**National Suicide Prevention Hotline (U.S.):**', - `**Call:** 1-800-273-8255, available 24/7 for emotional support -**Text: HOME** to 741741 -https://suicidepreventionlifeline.org/chat/ - -**Outside the U.S**: Find a supportive resource on [this Wikipedia list of worldwide crisis hotlines](https://en.wikipedia.org/wiki/List_of_suicide_crisis_lines)` + [ + '**Call:** 1-800-273-8255, available 24/7 for emotional support', + '**Text: HOME** to 741741', + 'https://suicidepreventionlifeline.org/chat/', + '', + '**Outside the U.S**: Find a supportive resource on [this Wikipedia list of worldwide crisis hotlines](https://en.wikipedia.org/wiki/List_of_suicide_crisis_lines)' + ].join('\n') ) .addField( '**More Support**', - `For Substance Abuse Support, Eating Disorder Support & Child Abuse and Domestic Violence: -[Click to go to Discord's Health & Safety Page](https://discord.com/safety/360044103771-Mental-health-on-Discord#h_01EGRGT08QSZ5BNCH2E9HN0NYV)` + [ + 'For Substance Abuse Support, Eating Disorder Support & Child Abuse and Domestic Violence:', + "[Click to go to Discord's Health & Safety Page](https://discord.com/safety/360044103771-Mental-health-on-Discord#h_01EGRGT08QSZ5BNCH2E9HN0NYV)" + ].join('\n') ); return ( diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts index 50280a0..9484729 100644 --- a/src/commands/utilities/uuid.ts +++ b/src/commands/utilities/uuid.ts @@ -14,7 +14,6 @@ export default class UuidCommand extends BushCommand { { id: 'ign', customType: /\w{1,16}/im, - prompt: { start: 'What ign would you like to find the uuid of?', retry: '{error} Choose a valid ign.', @@ -22,9 +21,8 @@ export default class UuidCommand extends BushCommand { } } ], - cooldown: 4000, - ratelimit: 1, - clientPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } diff --git a/src/commands/utilities/viewraw.ts b/src/commands/utilities/viewraw.ts index bd185d4..b7017c6 100644 --- a/src/commands/utilities/viewraw.ts +++ b/src/commands/utilities/viewraw.ts @@ -3,11 +3,9 @@ import { BushCommand, BushMessage, BushSlashMessage } from '../../lib'; export default class ViewRawCommand extends BushCommand { public constructor() { - super('viewraw', { - aliases: ['viewraw'], + super('view-raw', { + aliases: ['view-raw', 'vr'], category: 'utilities', - clientPermissions: ['EMBED_LINKS'], - channel: 'guild', description: { usage: 'viewraw <message id> <channel>', examples: ['viewraw 322862723090219008'], @@ -42,7 +40,37 @@ export default class ViewRawCommand extends BushCommand { match: 'flag', flag: '--js' } - ] + ], + slash: true, + slashOptions: [ + { + name: 'message', + description: 'What message would you like to view?', + type: 'STRING', + required: true + }, + { + name: 'channel', + description: 'What channel is the message in?', + type: 'CHANNEL', + required: false + }, + { + name: 'json', + description: 'Would you like to view the raw JSON message data?', + type: 'BOOLEAN', + required: false + }, + { + name: 'js', + description: 'Would you like to view the raw message data?', + type: 'BOOLEAN', + required: false + } + ], + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['EMBED_LINKS'], true), + userPermissions: [] }); } diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts index 1b72d65..bce88d6 100644 --- a/src/commands/utilities/whoHasRole.ts +++ b/src/commands/utilities/whoHasRole.ts @@ -1,5 +1,6 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { CommandInteraction, Role, Util } from 'discord.js'; +import { ButtonPaginator } from '../../lib/common/ButtonPaginator'; export default class WhoHasRoleCommand extends BushCommand { public constructor() { @@ -32,8 +33,8 @@ export default class WhoHasRoleCommand extends BushCommand { } ], channel: 'guild', - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'], + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [], typing: true }); } @@ -51,6 +52,6 @@ export default class WhoHasRoleCommand extends BushCommand { color })); - return await util.buttonPaginate(message, embedPages, null, true); + return await ButtonPaginator.send(message, embedPages, null, true); } } diff --git a/src/commands/utilities/wolframAlpha.ts b/src/commands/utilities/wolframAlpha.ts index 4a357b8..049dd60 100644 --- a/src/commands/utilities/wolframAlpha.ts +++ b/src/commands/utilities/wolframAlpha.ts @@ -1,5 +1,5 @@ import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { CommandInteraction, MessageEmbed } from 'discord.js'; +import { CommandInteraction, MessageEmbed, MessageOptions } from 'discord.js'; import WolframAlphaAPI from 'wolfram-alpha-api'; export default class WolframAlphaCommand extends BushCommand { @@ -13,6 +13,7 @@ export default class WolframAlphaCommand extends BushCommand { examples: ['wolfram-alpha what is the population of france'] }, args: [ + { id: 'image', match: 'flag', flag: '--image' }, { id: 'expression', type: 'string', @@ -31,30 +32,49 @@ export default class WolframAlphaCommand extends BushCommand { description: 'What would you like to look up?', type: 'STRING', required: true + }, + { + name: 'image', + description: 'Would you like to use the Simple API instead of the Short Answers API?', + type: 'BOOLEAN', + required: false } ], - clientPermissions: ['SEND_MESSAGES'], - userPermissions: ['SEND_MESSAGES'] + clientPermissions: (m) => util.clientSendAndPermCheck(m), + userPermissions: [] }); } - public override async exec(message: BushMessage | BushSlashMessage, args: { expression: string }): Promise<unknown> { + public override async exec( + message: BushMessage | BushSlashMessage, + args: { expression: string; image: boolean } + ): Promise<unknown> { if (message.util.isSlash) await (message.interaction as CommandInteraction).deferReply(); + args.image && void message.util.reply({ content: `${util.emojis.loading} Loading...`, embeds: [] }); const waApi = WolframAlphaAPI(client.config.credentials.wolframAlphaAppId); const decodedEmbed = new MessageEmbed().addField('📥 Input', await util.inspectCleanRedactCodeblock(args.expression)); + const sendOptions: MessageOptions = { content: null, allowedMentions: AllowedMentions.none() }; try { - const calculated = await waApi.getShort(args.expression); - decodedEmbed - .setTitle(`${util.emojis.successFull} Successfully Queried Expression`) - .setColor(util.colors.success) - .addField('📤 Output', await util.inspectCleanRedactCodeblock(calculated.toString())); + const calculated = await (args.image + ? waApi.getSimple({ i: args.expression, timeout: 1, background: '2C2F33', foreground: 'white' }) + : waApi.getShort(args.expression)); + decodedEmbed.setTitle(`${util.emojis.successFull} Successfully Queried Expression`).setColor(util.colors.success); + + if (args.image) { + decodedEmbed.setImage(await util.uploadImageToImgur(calculated.split(',')[1])); + decodedEmbed.addField('📤 Output', '​'); + } else { + decodedEmbed.addField('📤 Output', await util.inspectCleanRedactCodeblock(calculated.toString())); + } } catch (error) { decodedEmbed .setTitle(`${util.emojis.errorFull} Unable to Query Expression`) .setColor(util.colors.error) .addField(`📤 Error`, await util.inspectCleanRedactCodeblock(`${error.name}: ${error.message}`, 'js')); } - return await message.util.reply({ embeds: [decodedEmbed], allowedMentions: AllowedMentions.none() }); + sendOptions.embeds = [decodedEmbed]; + + return await message.util.reply(sendOptions); } } diff --git a/src/config/example-options.ts b/src/config/example-options.ts index c7838ab..01519b2 100644 --- a/src/config/example-options.ts +++ b/src/config/example-options.ts @@ -6,7 +6,9 @@ export default new Config({ betaToken: '[TOKEN]', devToken: '[TOKEN]', hypixelApiKey: '[API_KEY]', - wolframAlphaAppId: '[APP_ID]' + wolframAlphaAppId: '[APP_ID]', + imgurClientId: '[CLIENT_ID]', + imgurClientSecret: '[CLIENT_SECRET]' }, environment: 'development', owners: [ diff --git a/src/context-menu-commands/message/viewRaw.ts b/src/context-menu-commands/message/viewRaw.ts index c04ec3c..77fd0b9 100644 --- a/src/context-menu-commands/message/viewRaw.ts +++ b/src/context-menu-commands/message/viewRaw.ts @@ -1,6 +1,6 @@ import { ContextMenuCommand } from 'discord-akairo'; import { ContextMenuInteraction } from 'discord.js'; -import ViewRawCommand from '../../commands/utilities/viewraw'; +import ViewRawCommand from '../../commands/utilities/viewRaw'; import { BushMessage } from '../../lib'; export default class ViewRawContextMenuCommand extends ContextMenuCommand { diff --git a/src/lib/badlinks.ts b/src/lib/badlinks.ts index 933fce3..059d3d9 100644 --- a/src/lib/badlinks.ts +++ b/src/lib/badlinks.ts @@ -792,6 +792,7 @@ export default [ "discordcreators.net", "discordd.buzz", "discordd.gg", + "discordd.gift", "discorddaapp.com", "discorddev.com", "discorddiscord.com", diff --git a/src/lib/badwords.ts b/src/lib/badwords.ts index 975df24..e5033d7 100644 --- a/src/lib/badwords.ts +++ b/src/lib/badwords.ts @@ -1,4 +1,4 @@ -import { BadWords, Severity } from "./common/autoMod"; +import { BadWords, Severity } from "./common/AutoMod"; export default { /* -------------------------------------------------------------------------- */ diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts new file mode 100644 index 0000000..c74f6ad --- /dev/null +++ b/src/lib/common/ButtonPaginator.ts @@ -0,0 +1,186 @@ +import { + Constants, + MessageActionRow, + MessageButton, + MessageComponentInteraction, + MessageEmbed, + MessageEmbedOptions +} from 'discord.js'; +import { BushMessage, BushSlashMessage } from '..'; +import { DeleteButton } from './DeleteButton'; + +export class ButtonPaginator { + protected message: BushMessage | BushSlashMessage; + protected embeds: MessageEmbed[] | MessageEmbedOptions[]; + protected text: string | null; + protected deleteOnExit: boolean; + protected curPage: number; + protected sentMessage: BushMessage | undefined; + + /** + * Sends multiple embeds with controls to switch between them + * @param message - The message to respond to + * @param embeds - The embeds to switch between + * @param text - The text send with the embeds (optional) + * @param deleteOnExit - Whether to delete the message when the exit button is clicked (defaults to true) + * @param startOn - The page to start from (**not** the index) + */ + public static async send( + message: BushMessage | BushSlashMessage, + embeds: MessageEmbed[] | MessageEmbedOptions[], + text: string | null = null, + deleteOnExit = true, + startOn = 1 + ): Promise<void> { + // no need to paginate if there is only one page + if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds }); + + return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send(); + } + + protected get numPages(): number { + return this.embeds.length; + } + + protected constructor( + message: BushMessage | BushSlashMessage, + embeds: MessageEmbed[] | MessageEmbedOptions[], + text: string | null, + deleteOnExit: boolean, + startOn: number + ) { + this.message = message; + this.embeds = embeds; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.text = text || null; + this.deleteOnExit = deleteOnExit; + this.curPage = startOn - 1; + + // add footers + for (let i = 0; i < embeds.length; i++) { + if (embeds[i] instanceof MessageEmbed) { + (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`); + } else { + (embeds[i] as MessageEmbedOptions).footer = { + text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` + }; + } + } + } + + protected async send() { + this.sentMessage = (await this.message.util.reply({ + content: this.text, + embeds: [this.embeds[this.curPage]], + components: [this.getPaginationRow()] + })) as BushMessage; + + const collector = this.sentMessage.createMessageComponentCollector({ + filter: (i) => i.customId.startsWith('paginate_') && i.message.id === this.sentMessage!.id, + time: 300000 + }); + + collector.on('collect', (i) => void this.collect(i)); + collector.on('end', () => void this.end()); + } + + protected async collect(interaction: MessageComponentInteraction) { + if (interaction.user.id !== this.message.author.id || !client.config.owners.includes(interaction.user.id)) + return await interaction?.deferUpdate().catch(() => undefined); + + switch (interaction.customId) { + case 'paginate_beginning': + this.curPage = 0; + return this.edit(interaction); + case 'paginate_back': + this.curPage--; + return await this.edit(interaction); + case 'paginate_stop': + if (this.deleteOnExit) { + await interaction.deferUpdate().catch(() => undefined); + return await this.sentMessage!.delete().catch(() => undefined); + } else { + return await interaction + ?.update({ + content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`, + embeds: [], + components: [] + }) + .catch(() => undefined); + } + case 'paginate_next': + this.curPage++; + return await this.edit(interaction); + case 'paginate_end': + this.curPage = this.embeds.length - 1; + return await this.edit(interaction); + } + } + + protected async end() { + try { + return this.sentMessage!.edit({ + content: this.text, + embeds: [this.embeds[this.curPage]], + components: [this.getPaginationRow(true)] + }); + } catch (e) { + return undefined; + } + } + + protected async edit(interaction: MessageComponentInteraction) { + try { + return interaction?.update({ + content: this.text, + embeds: [this.embeds[this.curPage]], + components: [this.getPaginationRow()] + }); + } catch (e) { + return undefined; + } + } + + protected getPaginationRow(disableAll = false): MessageActionRow { + return new MessageActionRow().addComponents( + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate_beginning', + emoji: PaginateEmojis.BEGGING, + disabled: disableAll || this.curPage === 0 + }), + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate_back', + emoji: PaginateEmojis.BACK, + disabled: disableAll || this.curPage === 0 + }), + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate_stop', + emoji: PaginateEmojis.STOP, + disabled: disableAll + }), + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate_next', + emoji: PaginateEmojis.FORWARD, + disabled: disableAll || this.curPage === this.numPages - 1 + }), + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate_end', + emoji: PaginateEmojis.END, + disabled: disableAll || this.curPage === this.numPages - 1 + }) + ); + } +} + +export const enum PaginateEmojis { + BEGGING = '853667381335162910', + BACK = '853667410203770881', + STOP = '853667471110570034', + FORWARD = '853667492680564747', + END = '853667514915225640' +} diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts new file mode 100644 index 0000000..7d2e41b --- /dev/null +++ b/src/lib/common/DeleteButton.ts @@ -0,0 +1,61 @@ +import { Constants, MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions } from 'discord.js'; +import { BushMessage, BushSlashMessage } from '..'; +import { PaginateEmojis } from './ButtonPaginator'; + +export class DeleteButton { + protected messageOptions: MessageOptions; + protected message: BushMessage | BushSlashMessage; + + /** + * Sends a message with a button for the user to delete it. + * @param message - The message to respond to + * @param options - The send message options + */ + static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) { + return new DeleteButton(message, options).send(); + } + + protected constructor(message: BushMessage | BushSlashMessage, options: MessageOptions) { + this.message = message; + this.messageOptions = options; + } + + protected async send() { + this.updateComponents(); + + const msg = (await this.message.util.reply(this.messageOptions)) as BushMessage; + + const collector = msg.createMessageComponentCollector({ + filter: (interaction) => interaction.customId == 'paginate__stop' && interaction.message.id == msg.id, + time: 300000 + }); + + collector.on('collect', async (interaction: MessageComponentInteraction) => { + await interaction.deferUpdate().catch(() => undefined); + if (interaction.user.id == this.message.author.id || client.config.owners.includes(interaction.user.id)) { + if (msg.deletable && !msg.deleted) await msg.delete(); + } + }); + + collector.on('end', async () => { + this.updateComponents(true, true); + await msg.edit(this.messageOptions).catch(() => undefined); + }); + } + + protected updateComponents(edit = false, disable = false): void { + this.messageOptions.components = [ + new MessageActionRow().addComponents( + new MessageButton({ + style: Constants.MessageButtonStyles.PRIMARY, + customId: 'paginate__stop', + emoji: PaginateEmojis.STOP, + disabled: disable + }) + ) + ]; + if (edit) { + this.messageOptions.reply = undefined; + } + } +} diff --git a/src/lib/common/Format.ts b/src/lib/common/Format.ts new file mode 100644 index 0000000..ba1ee9f --- /dev/null +++ b/src/lib/common/Format.ts @@ -0,0 +1,72 @@ +import { Formatters, Util } from 'discord.js'; +import { CodeBlockLang } from './typings/CodeBlockLang'; + +/** + * Formats and escapes content for formatting + */ +export class Format { + /** + * Wraps the content inside a codeblock with no language. + * @param content The content to wrap. + */ + public static codeBlock(content: string): string; + /** + * Wraps the content inside a codeblock with the specified language. + * @param language The language for the codeblock. + * @param content The content to wrap. + */ + public static codeBlock(language: CodeBlockLang, content: string): string; + public static codeBlock(languageOrContent: string, content?: string): string { + return typeof content === 'undefined' + ? Formatters.codeBlock(Util.escapeCodeBlock(languageOrContent)) + : Formatters.codeBlock(languageOrContent, Util.escapeCodeBlock(languageOrContent)); + } + + /** + * Wraps the content inside \`backticks\`, which formats it as inline code. + * @param content The content to wrap. + */ + public static inlineCode(content: string): string { + return Formatters.inlineCode(Util.escapeInlineCode(content)); + } + + /** + * Formats the content into italic text. + * @param content The content to wrap. + */ + public static italic(content: string): string { + return Formatters.italic(Util.escapeItalic(content)); + } + + /** + * Formats the content into bold text. + * @param content The content to wrap. + */ + public static bold(content: string): string { + return Formatters.bold(Util.escapeBold(content)); + } + + /** + * Formats the content into underscored text. + * @param content The content to wrap. + */ + public static underscore(content: string): string { + return Formatters.underscore(Util.escapeUnderline(content)); + } + + /** + * Formats the content into strike-through text. + * @param content The content to wrap. + */ + public static strikethrough(content: string): string { + return Formatters.strikethrough(Util.escapeStrikethrough(content)); + } + + /** + * Wraps the content inside spoiler (hidden text). + * @param content The content to wrap. + */ + public static spoiler(content: string): string { + return Formatters.spoiler(Util.escapeSpoiler(content)); + } +} diff --git a/src/lib/common/autoMod.ts b/src/lib/common/autoMod.ts index 0bdbebf..312beb3 100644 --- a/src/lib/common/autoMod.ts +++ b/src/lib/common/autoMod.ts @@ -1,17 +1,17 @@ -import { Formatters, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js'; -import badLinksArray from '../../lib/badlinks'; -import badLinksSecretArray from '../../lib/badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array -import badWords from '../../lib/badwords'; +import { GuildMember, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js'; +import badLinksArray from '../badlinks'; +import badLinksSecretArray from '../badlinks-secret'; // I cannot make this public so just make a new file that export defaults an empty array +import badWords from '../badwords'; import { BushButtonInteraction } from '../extensions/discord.js/BushButtonInteraction'; -import { BushGuildMember } from '../extensions/discord.js/BushGuildMember'; import { BushMessage } from '../extensions/discord.js/BushMessage'; -import { Moderation } from './moderation'; +import { Moderation } from './Moderation'; export class AutoMod { private message: BushMessage; public constructor(message: BushMessage) { this.message = message; + if (message.author.id === client.user?.id) return; void this.handle(); } @@ -21,17 +21,10 @@ export class AutoMod { const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? {}; const badLinks: BadWords = {}; - const badLinksSecret: BadWords = {}; - badLinksArray.forEach((link) => { - badLinks[link] = { - severity: Severity.PERM_MUTE, - ignoreSpaces: true, - ignoreCapitalization: true, - reason: 'malicious link' - }; - }); - badLinksSecretArray.forEach((link) => { + const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])]; + + uniqueLinks.forEach((link) => { badLinks[link] = { severity: Severity.PERM_MUTE, ignoreSpaces: true, @@ -43,9 +36,7 @@ export class AutoMod { const result = { ...this.checkWords(customAutomodPhrases), ...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? {} : badWords), - ...this.checkWords( - (await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : { ...badLinks, ...badLinksSecret } - ) + ...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? {} : badLinks) }; if (Object.keys(result).length === 0) return; @@ -59,9 +50,7 @@ export class AutoMod { embeds: [ { title: 'AutoMod Error', - description: `Unable to find severity information for ${Formatters.inlineCode( - util.discord.escapeInlineCode(highestOffence.word) - )}`, + description: `Unable to find severity information for ${util.format.inlineCode(highestOffence.word)}`, color: util.colors.error } ] @@ -128,7 +117,7 @@ export class AutoMod { break; } default: { - throw new Error('Invalid severity'); + throw new Error(`Invalid severity: ${highestOffence.severity}`); } } @@ -163,8 +152,8 @@ export class AutoMod { .setDescription( `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From**: <#${ this.message.channel.id - }> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${util - .surroundArray(Object.keys(offences), '`') + }> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${Object.keys(offences) + .map((key) => `\`${key}\``) .join(', ')}` ) .addField('Message Content', `${await util.codeblock(this.message.content, 1024)}`) @@ -194,12 +183,13 @@ export class AutoMod { const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';'); switch (action) { case 'ban': { - const check = await Moderation.permissionCheck( - interaction.member as BushGuildMember, - interaction.guild!.members.cache.get(userId)!, - 'ban', - true - ); + const victim = await interaction.guild!.members.fetch(userId); + const moderator = + interaction.member instanceof GuildMember + ? interaction.member + : await interaction.guild!.members.fetch(interaction.user.id); + + const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; if (check !== true) return interaction.reply({ diff --git a/src/lib/common/moderation.ts b/src/lib/common/moderation.ts index c8779fc..29d66fa 100644 --- a/src/lib/common/moderation.ts +++ b/src/lib/common/moderation.ts @@ -123,7 +123,7 @@ export class Moderation { const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined; const user = (await util.resolveNonCachedUser(options.user))!.id; const guild = client.guilds.resolveId(options.guild)!; - const type = this.#findTypeEnum(options.type)!; + const type = this.findTypeEnum(options.type)!; const entry = ActivePunishment.build( options.extraInfo @@ -144,7 +144,7 @@ export class Moderation { }): Promise<boolean> { const user = await util.resolveNonCachedUser(options.user); const guild = client.guilds.resolveId(options.guild); - const type = this.#findTypeEnum(options.type); + const type = this.findTypeEnum(options.type); if (!user || !guild) return false; @@ -160,18 +160,19 @@ export class Moderation { success = false; }); if (entries) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - entries.forEach(async (entry) => { - await entry.destroy().catch(async (e) => { + const promises = entries.map(async (entry) => + entry.destroy().catch(async (e) => { await util.handleError('removePunishmentEntry', e); - }); - success = false; - }); + success = false; + }) + ); + + await Promise.all(promises); } return success; } - static #findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { + private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { const typeMap = { ['mute']: ActivePunishmentType.MUTE, ['ban']: ActivePunishmentType.BAN, diff --git a/src/lib/common/typings/BushInspectOptions.d.ts b/src/lib/common/typings/BushInspectOptions.d.ts new file mode 100644 index 0000000..c2a2360 --- /dev/null +++ b/src/lib/common/typings/BushInspectOptions.d.ts @@ -0,0 +1,91 @@ +import { InspectOptions } from 'util'; + +/** + * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options} + */ +export interface BushInspectOptions extends InspectOptions { + /** + * If `true`, object's non-enumerable symbols and properties are included in the + * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries are also included as well as + * user defined prototype properties (excluding method properties). + * + * **Default**: `false`. + */ + showHidden?: boolean | undefined; + /** + * Specifies the number of times to recurse while formatting `object`. This is useful + * for inspecting large objects. To recurse up to the maximum call stack size pass + * `Infinity` or `null`. + * + * **Default**: `2`. + */ + depth?: number | null | undefined; + /** + * If `true`, the output is styled with ANSI color codes. Colors are customizable. See [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). + * + * **Default**: `false`. + */ + colors?: boolean | undefined; + /** + * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. + * + * **Default**: `true`. + */ + customInspect?: boolean | undefined; + /** + * If `true`, `Proxy` inspection includes the [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) objects. + * + * **Default**: `false`. + */ + showProxy?: boolean | undefined; + /** + * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and + * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to include when formatting. Set to `null` or `Infinity` to + * show all elements. Set to `0` or negative to show no elements. + * + * **Default**: `100`. + */ + maxArrayLength?: number | null | undefined; + /** + * Specifies the maximum number of characters to include when formatting. Set to + * `null` or `Infinity` to show all elements. Set to `0` or negative to show no + * characters. + * + * **Default**: `10000`. + */ + maxStringLength?: number | null | undefined; + /** + * The length at which input values are split across multiple lines. Set to + * `Infinity` to format the input as a single line (in combination with compact set + * to `true` or any number >= `1`). + * + * **Default**: `80`. + */ + breakLength?: number | undefined; + /** + * Setting this to `false` causes each object key to be displayed on a new line. It + * will break on new lines in text that is longer than `breakLength`. If set to a + * number, the most `n` inner elements are united on a single line as long as all + * properties fit into `breakLength`. Short array elements are also grouped together. + * + * **Default**: `3` + */ + compact?: boolean | number | undefined; + /** + * If set to `true` or a function, all properties of an object, and `Set` and `Map` + * entries are sorted in the resulting string. If set to `true` the [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. + * If set to a function, it is used as a [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). + * + * **Default**: `false`. + */ + sorted?: boolean | ((a: string, b: string) => number) | undefined; + /** + * If set to `true`, getters are inspected. If set to `'get'`, only getters without a + * corresponding setter are inspected. If set to `'set'`, only getters with a + * corresponding setter are inspected. This might cause side effects depending on + * the getter function. + * + * **Default**: `false`. + */ + getters?: 'get' | 'set' | boolean | undefined; +} diff --git a/src/lib/common/typings/CodeBlockLang.d.ts b/src/lib/common/typings/CodeBlockLang.d.ts new file mode 100644 index 0000000..5a1aeba --- /dev/null +++ b/src/lib/common/typings/CodeBlockLang.d.ts @@ -0,0 +1,310 @@ +export type CodeBlockLang = + | '1c' + | 'abnf' + | 'accesslog' + | 'actionscript' + | 'ada' + | 'arduino' + | 'ino' + | 'armasm' + | 'arm' + | 'avrasm' + | 'actionscript' + | 'as' + | 'angelscript' + | 'asc' + | 'apache' + | 'apacheconf' + | 'applescript' + | 'osascript' + | 'arcade' + | 'asciidoc' + | 'adoc' + | 'aspectj' + | 'autohotkey' + | 'autoit' + | 'awk' + | 'mawk' + | 'nawk' + | 'gawk' + | 'bash' + | 'sh' + | 'zsh' + | 'basic' + | 'bnf' + | 'brainfuck' + | 'bf' + | 'csharp' + | 'cs' + | 'c' + | 'h' + | 'cpp' + | 'hpp' + | 'cc' + | 'hh' + | 'c++' + | 'h++' + | 'cxx' + | 'hxx' + | 'cal' + | 'cos' + | 'cls' + | 'cmake' + | 'cmake.in' + | 'coq' + | 'csp' + | 'css' + | 'capnproto' + | 'capnp' + | 'clojure' + | 'clj' + | 'coffeescript' + | 'coffee' + | 'cson' + | 'iced' + | 'crmsh' + | 'crm' + | 'pcmk' + | 'crystal' + | 'cr' + | 'd' + | 'dns' + | 'zone' + | 'bind' + | 'dos' + | 'bat' + | 'cmd' + | 'dart' + | 'dpr' + | 'dfm' + | 'pas' + | 'pascal' + | 'diff' + | 'patch' + | 'django' + | 'jinja' + | 'dockerfile' + | 'docker' + | 'dsconfig' + | 'dts' + | 'dust' + | 'dst' + | 'ebnf' + | 'elixir' + | 'elm' + | 'erlang' + | 'erl' + | 'excel' + | 'xls' + | 'xlsx' + | 'fsharp' + | 'fs' + | 'fix' + | 'fortran' + | 'f90' + | 'f95' + | 'gcode' + | 'nc' + | 'gams' + | 'gms' + | 'gauss' + | 'gss' + | 'gherkin' + | 'go' + | 'golang' + | 'golo' + | 'gololang' + | 'gradle' + | 'groovy' + | 'xml' + | 'html' + | 'xhtml' + | 'rss' + | 'atom' + | 'xjb' + | 'xsd' + | 'xsl' + | 'plist' + | 'svg' + | 'http' + | 'https' + | 'haml' + | 'handlebars' + | 'hbs' + | 'html.hbs' + | 'html.handlebars' + | 'haskell' + | 'hs' + | 'haxe' + | 'hx' + | 'hlsl' + | 'hy' + | 'hylang' + | 'ini' + | 'toml' + | 'inform7' + | 'i7' + | 'irpf90' + | 'json' + | 'java' + | 'jsp' + | 'javascript' + | 'js' + | 'jsx' + | 'julia' + | 'julia-repl' + | 'kotlin' + | 'kt' + | 'tex' + | 'leaf' + | 'lasso' + | 'ls' + | 'lassoscript' + | 'less' + | 'ldif' + | 'lisp' + | 'livecodeserver' + | 'livescript' + | 'ls' + | 'lua' + | 'makefile' + | 'mk' + | 'mak' + | 'make' + | 'markdown' + | 'md' + | 'mkdown' + | 'mkd' + | 'mathematica' + | 'mma' + | 'wl' + | 'matlab' + | 'maxima' + | 'mel' + | 'mercury' + | 'mizar' + | 'mojolicious' + | 'monkey' + | 'moonscript' + | 'moon' + | 'n1ql' + | 'nsis' + | 'nginx' + | 'nginxconf' + | 'nim' + | 'nimrod' + | 'nix' + | 'ocaml' + | 'ml' + | 'objectivec' + | 'mm' + | 'objc' + | 'obj-c' + | 'obj-c++' + | 'objective-c++' + | 'glsl' + | 'openscad' + | 'scad' + | 'ruleslanguage' + | 'oxygene' + | 'pf' + | 'pf.conf' + | 'php' + | 'parser3' + | 'perl' + | 'pl' + | 'pm' + | 'plaintext' + | 'txt' + | 'text' + | 'pony' + | 'pgsql' + | 'postgres' + | 'postgresql' + | 'powershell' + | 'ps' + | 'ps1' + | 'processing' + | 'prolog' + | 'properties' + | 'protobuf' + | 'puppet' + | 'pp' + | 'python' + | 'py' + | 'gyp' + | 'profile' + | 'python-repl' + | 'pycon' + | 'k' + | 'kdb' + | 'qml' + | 'r' + | 'reasonml' + | 're' + | 'rib' + | 'rsl' + | 'graph' + | 'instances' + | 'ruby' + | 'rb' + | 'gemspec' + | 'podspec' + | 'thor' + | 'irb' + | 'rust' + | 'rs' + | 'sas' + | 'scss' + | 'sql' + | 'p21' + | 'step' + | 'stp' + | 'scala' + | 'scheme' + | 'scilab' + | 'sci' + | 'shell' + | 'console' + | 'smali' + | 'smalltalk' + | 'st' + | 'sml' + | 'ml' + | 'stan' + | 'stanfuncs' + | 'stata' + | 'stylus' + | 'styl' + | 'subunit' + | 'swift' + | 'tcl' + | 'tk' + | 'tap' + | 'thrift' + | 'tp' + | 'twig' + | 'craftcms' + | 'typescript' + | 'ts' + | 'vbnet' + | 'vb' + | 'vbscript' + | 'vbs' + | 'vhdl' + | 'vala' + | 'verilog' + | 'v' + | 'vim' + | 'axapta' + | 'x++' + | 'x86asm' + | 'xl' + | 'tao' + | 'xquery' + | 'xpath' + | 'xq' + | 'yml' + | 'yaml' + | 'zephir' + | 'zep'; diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts new file mode 100644 index 0000000..84d5aeb --- /dev/null +++ b/src/lib/common/util/Arg.ts @@ -0,0 +1,120 @@ +import { Argument, ArgumentTypeCaster, Flag, ParsedValuePredicate, TypeResolver } from 'discord-akairo'; +import { Message } from 'discord.js'; +import { BushArgumentType } from '../..'; + +export class Arg { + /** + * Casts a phrase to this argument's type. + * @param type - The type to cast to. + * @param resolver - The type resolver. + * @param message - Message that called the command. + * @param phrase - Phrase to process. + */ + public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> { + return Argument.cast(type, resolver, message, phrase); + } + + /** + * Creates a type that is the left-to-right composition of the given types. + * If any of the types fails, the entire composition fails. + * @param types - Types to use. + */ + public static compose(...types: BushArgumentType[]): ArgumentTypeCaster { + return Argument.compose(...types); + } + + /** + * Creates a type that is the left-to-right composition of the given types. + * If any of the types fails, the composition still continues with the failure passed on. + * @param types - Types to use. + */ + public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster { + return Argument.composeWithFailure(...types); + } + + /** + * Checks if something is null, undefined, or a fail flag. + * @param value - Value to check. + */ + public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) { + return Argument.isFailure(value); + } + + /** + * Creates a type from multiple types (product type). + * Only inputs where each type resolves with a non-void value are valid. + * @param types - Types to use. + */ + public static product(...types: BushArgumentType[]): ArgumentTypeCaster { + return Argument.product(...types); + } + + /** + * Creates a type where the parsed value must be within a range. + * @param type - The type to use. + * @param min - Minimum value. + * @param max - Maximum value. + * @param inclusive - Whether or not to be inclusive on the upper bound. + */ + public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster { + return Argument.range(type, min, max, inclusive); + } + + /** + * Creates a type that parses as normal but also tags it with some data. + * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed. + * @param type - The type to use. + * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. + */ + public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster { + return Argument.tagged(type, tag); + } + + /** + * Creates a type from multiple types (union type). + * The first type that resolves to a non-void value is used. + * Each type will also be tagged using `tagged` with themselves. + * @param types - Types to use. + */ + public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster { + return Argument.taggedUnion(...types); + } + + /** + * Creates a type that parses as normal but also tags it with some data and carries the original input. + * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed. + * @param type - The type to use. + * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. + */ + public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster { + return Argument.taggedWithInput(type, tag); + } + + /** + * Creates a type from multiple types (union type). + * The first type that resolves to a non-void value is used. + * @param types - Types to use. + */ + public static union(...types: BushArgumentType[]): ArgumentTypeCaster { + return Argument.union(...types); + } + + /** + * Creates a type with extra validation. + * If the predicate is not true, the value is considered invalid. + * @param type - The type to use. + * @param predicate - The predicate function. + */ + public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster { + return Argument.validate(type, predicate); + } + + /** + * Creates a type that parses as normal but also carries the original input. + * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed. + * @param type - The type to use. + */ + public static withInput(type: BushArgumentType): ArgumentTypeCaster { + return Argument.withInput(type); + } +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 448eaf3..499d2c7 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -1,5 +1,4 @@ import { - BushArgumentType, BushCache, BushClient, BushConstants, @@ -11,30 +10,16 @@ import { PronounCode } from '@lib'; import { exec } from 'child_process'; -import { - Argument, - ArgumentTypeCaster, - ClientUtil, - Flag, - ParsedValuePredicate, - TypeResolver, - Util as AkairoUtil -} from 'discord-akairo'; +import { ClientUtil, Util as AkairoUtil } from 'discord-akairo'; import { APIMessage } from 'discord-api-types'; import { ColorResolvable, CommandInteraction, - Constants, GuildMember, InteractionReplyOptions, Message, - MessageActionRow, - MessageButton, - MessageComponentInteraction, - MessageEditOptions, MessageEmbed, - MessageEmbedOptions, - MessageOptions, + PermissionResolvable, Snowflake, TextChannel, ThreadMember, @@ -46,443 +31,16 @@ import got from 'got'; import humanizeDuration from 'humanize-duration'; import _ from 'lodash'; import moment from 'moment'; -import { inspect, InspectOptions, promisify } from 'util'; +import { inspect, promisify } from 'util'; import CommandErrorListener from '../../../listeners/commands/commandError'; +import { Format } from '../../common/Format'; +import { BushInspectOptions } from '../../common/typings/BushInspectOptions'; +import { CodeBlockLang } from '../../common/typings/CodeBlockLang'; +import { Arg } from '../../common/util/Arg'; import { BushNewsChannel } from '../discord.js/BushNewsChannel'; import { BushTextChannel } from '../discord.js/BushTextChannel'; import { BushSlashEditMessageType, BushSlashSendMessageType, BushUserResolvable } from './BushClient'; -interface hastebinRes { - key: string; -} - -export interface uuidRes { - uuid: string; - username: string; - username_history?: { username: string }[] | null; - textures: { - custom: boolean; - slim: boolean; - skin: { - url: string; - data: string; - }; - raw: { - value: string; - signature: string; - }; - }; - created_at: string; -} - -interface MojangProfile { - username: string; - uuid: string; -} - -// #region codeblock type -export type CodeBlockLang = - | '1c' - | 'abnf' - | 'accesslog' - | 'actionscript' - | 'ada' - | 'arduino' - | 'ino' - | 'armasm' - | 'arm' - | 'avrasm' - | 'actionscript' - | 'as' - | 'angelscript' - | 'asc' - | 'apache' - | 'apacheconf' - | 'applescript' - | 'osascript' - | 'arcade' - | 'asciidoc' - | 'adoc' - | 'aspectj' - | 'autohotkey' - | 'autoit' - | 'awk' - | 'mawk' - | 'nawk' - | 'gawk' - | 'bash' - | 'sh' - | 'zsh' - | 'basic' - | 'bnf' - | 'brainfuck' - | 'bf' - | 'csharp' - | 'cs' - | 'c' - | 'h' - | 'cpp' - | 'hpp' - | 'cc' - | 'hh' - | 'c++' - | 'h++' - | 'cxx' - | 'hxx' - | 'cal' - | 'cos' - | 'cls' - | 'cmake' - | 'cmake.in' - | 'coq' - | 'csp' - | 'css' - | 'capnproto' - | 'capnp' - | 'clojure' - | 'clj' - | 'coffeescript' - | 'coffee' - | 'cson' - | 'iced' - | 'crmsh' - | 'crm' - | 'pcmk' - | 'crystal' - | 'cr' - | 'd' - | 'dns' - | 'zone' - | 'bind' - | 'dos' - | 'bat' - | 'cmd' - | 'dart' - | 'dpr' - | 'dfm' - | 'pas' - | 'pascal' - | 'diff' - | 'patch' - | 'django' - | 'jinja' - | 'dockerfile' - | 'docker' - | 'dsconfig' - | 'dts' - | 'dust' - | 'dst' - | 'ebnf' - | 'elixir' - | 'elm' - | 'erlang' - | 'erl' - | 'excel' - | 'xls' - | 'xlsx' - | 'fsharp' - | 'fs' - | 'fix' - | 'fortran' - | 'f90' - | 'f95' - | 'gcode' - | 'nc' - | 'gams' - | 'gms' - | 'gauss' - | 'gss' - | 'gherkin' - | 'go' - | 'golang' - | 'golo' - | 'gololang' - | 'gradle' - | 'groovy' - | 'xml' - | 'html' - | 'xhtml' - | 'rss' - | 'atom' - | 'xjb' - | 'xsd' - | 'xsl' - | 'plist' - | 'svg' - | 'http' - | 'https' - | 'haml' - | 'handlebars' - | 'hbs' - | 'html.hbs' - | 'html.handlebars' - | 'haskell' - | 'hs' - | 'haxe' - | 'hx' - | 'hlsl' - | 'hy' - | 'hylang' - | 'ini' - | 'toml' - | 'inform7' - | 'i7' - | 'irpf90' - | 'json' - | 'java' - | 'jsp' - | 'javascript' - | 'js' - | 'jsx' - | 'julia' - | 'julia-repl' - | 'kotlin' - | 'kt' - | 'tex' - | 'leaf' - | 'lasso' - | 'ls' - | 'lassoscript' - | 'less' - | 'ldif' - | 'lisp' - | 'livecodeserver' - | 'livescript' - | 'ls' - | 'lua' - | 'makefile' - | 'mk' - | 'mak' - | 'make' - | 'markdown' - | 'md' - | 'mkdown' - | 'mkd' - | 'mathematica' - | 'mma' - | 'wl' - | 'matlab' - | 'maxima' - | 'mel' - | 'mercury' - | 'mizar' - | 'mojolicious' - | 'monkey' - | 'moonscript' - | 'moon' - | 'n1ql' - | 'nsis' - | 'nginx' - | 'nginxconf' - | 'nim' - | 'nimrod' - | 'nix' - | 'ocaml' - | 'ml' - | 'objectivec' - | 'mm' - | 'objc' - | 'obj-c' - | 'obj-c++' - | 'objective-c++' - | 'glsl' - | 'openscad' - | 'scad' - | 'ruleslanguage' - | 'oxygene' - | 'pf' - | 'pf.conf' - | 'php' - | 'parser3' - | 'perl' - | 'pl' - | 'pm' - | 'plaintext' - | 'txt' - | 'text' - | 'pony' - | 'pgsql' - | 'postgres' - | 'postgresql' - | 'powershell' - | 'ps' - | 'ps1' - | 'processing' - | 'prolog' - | 'properties' - | 'protobuf' - | 'puppet' - | 'pp' - | 'python' - | 'py' - | 'gyp' - | 'profile' - | 'python-repl' - | 'pycon' - | 'k' - | 'kdb' - | 'qml' - | 'r' - | 'reasonml' - | 're' - | 'rib' - | 'rsl' - | 'graph' - | 'instances' - | 'ruby' - | 'rb' - | 'gemspec' - | 'podspec' - | 'thor' - | 'irb' - | 'rust' - | 'rs' - | 'sas' - | 'scss' - | 'sql' - | 'p21' - | 'step' - | 'stp' - | 'scala' - | 'scheme' - | 'scilab' - | 'sci' - | 'shell' - | 'console' - | 'smali' - | 'smalltalk' - | 'st' - | 'sml' - | 'ml' - | 'stan' - | 'stanfuncs' - | 'stata' - | 'stylus' - | 'styl' - | 'subunit' - | 'swift' - | 'tcl' - | 'tk' - | 'tap' - | 'thrift' - | 'tp' - | 'twig' - | 'craftcms' - | 'typescript' - | 'ts' - | 'vbnet' - | 'vb' - | 'vbscript' - | 'vbs' - | 'vhdl' - | 'vala' - | 'verilog' - | 'v' - | 'vim' - | 'axapta' - | 'x++' - | 'x86asm' - | 'xl' - | 'tao' - | 'xquery' - | 'xpath' - | 'xq' - | 'yml' - | 'yaml' - | 'zephir' - | 'zep'; -//#endregion - -/** - * {@link https://nodejs.org/api/util.html#util_util_inspect_object_options} - */ -export interface BushInspectOptions extends InspectOptions { - /** - * If `true`, object's non-enumerable symbols and properties are included in the - * formatted result. [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) entries are also included as well as - * user defined prototype properties (excluding method properties). - * - * **Default**: `false`. - */ - showHidden?: boolean | undefined; - /** - * Specifies the number of times to recurse while formatting `object`. This is useful - * for inspecting large objects. To recurse up to the maximum call stack size pass - * `Infinity` or `null`. - * - * **Default**: `2`. - */ - depth?: number | null | undefined; - /** - * If `true`, the output is styled with ANSI color codes. Colors are customizable. See [Customizing util.inspect colors](https://nodejs.org/api/util.html#util_customizing_util_inspect_colors). - * - * **Default**: `false`. - */ - colors?: boolean | undefined; - /** - * If `false`, `[util.inspect.custom](depth, opts)` functions are not invoked. - * - * **Default**: `true`. - */ - customInspect?: boolean | undefined; - /** - * If `true`, `Proxy` inspection includes the [`target` and `handler`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Terminology) objects. - * - * **Default**: `false`. - */ - showProxy?: boolean | undefined; - /** - * Specifies the maximum number of `Array`, [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) and - * [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) elements to include when formatting. Set to `null` or `Infinity` to - * show all elements. Set to `0` or negative to show no elements. - * - * **Default**: `100`. - */ - maxArrayLength?: number | null | undefined; - /** - * Specifies the maximum number of characters to include when formatting. Set to - * `null` or `Infinity` to show all elements. Set to `0` or negative to show no - * characters. - * - * **Default**: `10000`. - */ - maxStringLength?: number | null | undefined; - /** - * The length at which input values are split across multiple lines. Set to - * `Infinity` to format the input as a single line (in combination with compact set - * to `true` or any number >= `1`). - * - * **Default**: `80`. - */ - breakLength?: number | undefined; - /** - * Setting this to `false` causes each object key to be displayed on a new line. It - * will break on new lines in text that is longer than `breakLength`. If set to a - * number, the most `n` inner elements are united on a single line as long as all - * properties fit into `breakLength`. Short array elements are also grouped together. - * - * **Default**: `3` - */ - compact?: boolean | number | undefined; - /** - * If set to `true` or a function, all properties of an object, and `Set` and `Map` - * entries are sorted in the resulting string. If set to `true` the [default sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) is used. - * If set to a function, it is used as a [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters). - * - * **Default**: `false`. - */ - sorted?: boolean | ((a: string, b: string) => number) | undefined; - /** - * If set to `true`, getters are inspected. If set to `'get'`, only getters without a - * corresponding setter are inspected. If set to `'set'`, only getters with a - * corresponding setter are inspected. This might cause side effects depending on - * the getter function. - * - * **Default**: `false`. - */ - getters?: 'get' | 'set' | boolean | undefined; -} - export class BushClientUtil extends ClientUtil { /** * The client. @@ -504,22 +62,6 @@ export class BushClientUtil extends ClientUtil { ]; /** - * Emojis used for {@link BushClientUtil.buttonPaginate} - */ - #paginateEmojis = { - beginning: '853667381335162910', - back: '853667410203770881', - stop: '853667471110570034', - forward: '853667492680564747', - end: '853667514915225640' - }; - - /** - * A simple promise exec method - */ - #exec = promisify(exec); - - /** * Creates this client util * @param client The client to initialize with */ @@ -554,7 +96,7 @@ export class BushClientUtil extends ClientUtil { stdout: string; stderr: string; }> { - return await this.#exec(command); + return await promisify(exec)(command); } /** @@ -677,193 +219,13 @@ export class BushClientUtil extends ClientUtil { } /** - * Paginates an array of embeds using buttons. - */ - public async buttonPaginate( - message: BushMessage | BushSlashMessage, - embeds: MessageEmbed[] | MessageEmbedOptions[], - text: string | null = null, - deleteOnExit?: boolean, - startOn?: number - ): Promise<void> { - const paginateEmojis = this.#paginateEmojis; - if (deleteOnExit === undefined) deleteOnExit = true; - - if (embeds.length === 1) { - return this.sendWithDeleteButton(message, { embeds: embeds }); - } - - embeds.forEach((_e, i) => { - embeds[i] instanceof MessageEmbed - ? (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`) - : ((embeds[i] as MessageEmbedOptions).footer = { - text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` - }); - }); - - const style = Constants.MessageButtonStyles.PRIMARY; - let curPage = startOn ? startOn - 1 : 0; - if (typeof embeds !== 'object') throw new Error('embeds must be an object'); - const msg = (await message.util.reply({ - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - content: text || null, - embeds: [embeds[curPage]], - components: [getPaginationRow()] - })) as Message; - const filter = (interaction: MessageComponentInteraction) => - interaction.customId.startsWith('paginate_') && interaction.message.id === msg.id; - const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); - collector.on('collect', async (interaction: MessageComponentInteraction) => { - if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) { - switch (interaction.customId) { - case 'paginate_beginning': { - curPage = 0; - await edit(interaction); - break; - } - case 'paginate_back': { - curPage--; - await edit(interaction); - break; - } - case 'paginate_stop': { - if (deleteOnExit) { - await interaction.deferUpdate().catch(() => undefined); - if (msg.deletable && !msg.deleted) { - await msg.delete(); - } - } else { - await interaction - ?.update({ - content: `${text ? `${text}\n` : ''}Command closed by user.`, - embeds: [], - components: [] - }) - .catch(() => undefined); - } - return; - } - case 'paginate_next': { - curPage++; - await edit(interaction); - break; - } - case 'paginate_end': { - curPage = embeds.length - 1; - await edit(interaction); - break; - } - } - } else { - return await interaction?.deferUpdate().catch(() => undefined); - } - }); - - collector.on('end', async () => { - await msg - .edit({ - content: text, - embeds: [embeds[curPage]], - components: [getPaginationRow(true)] - }) - .catch(() => undefined); - }); - - async function edit(interaction: MessageComponentInteraction): Promise<void> { - return await interaction - ?.update({ content: text, embeds: [embeds[curPage]], components: [getPaginationRow()] }) - .catch(() => undefined); - } - function getPaginationRow(disableAll = false): MessageActionRow { - return new MessageActionRow().addComponents( - new MessageButton({ - style, - customId: 'paginate_beginning', - emoji: paginateEmojis.beginning, - disabled: disableAll || curPage === 0 - }), - new MessageButton({ - style, - customId: 'paginate_back', - emoji: paginateEmojis.back, - disabled: disableAll || curPage === 0 - }), - new MessageButton({ - style, - customId: 'paginate_stop', - emoji: paginateEmojis.stop, - disabled: disableAll - }), - new MessageButton({ - style, - customId: 'paginate_next', - emoji: paginateEmojis.forward, - disabled: disableAll || curPage === embeds.length - 1 - }), - new MessageButton({ - style, - customId: 'paginate_end', - emoji: paginateEmojis.end, - disabled: disableAll || curPage === embeds.length - 1 - }) - ); - } - } - - /** - * Sends a message with a button for the user to delete it. - */ - public async sendWithDeleteButton(message: BushMessage | BushSlashMessage, options: MessageOptions): Promise<void> { - const paginateEmojis = this.#paginateEmojis; - updateOptions(); - const msg = (await message.util.reply(options as MessageOptions & { split?: false })) as Message; - const filter = (interaction: MessageComponentInteraction) => - interaction.customId == 'paginate__stop' && interaction.message == msg; - const collector = msg.createMessageComponentCollector({ filter, time: 300000 }); - collector.on('collect', async (interaction: MessageComponentInteraction) => { - if (interaction.user.id == message.author.id || client.config.owners.includes(interaction.user.id)) { - await interaction.deferUpdate().catch(() => undefined); - if (msg.deletable && !msg.deleted) { - await msg.delete(); - } - return; - } else { - return await interaction?.deferUpdate().catch(() => undefined); - } - }); - - collector.on('end', async () => { - updateOptions(true, true); - await msg.edit(options as MessageEditOptions).catch(() => undefined); - }); - - function updateOptions(edit?: boolean, disable?: boolean) { - if (edit == undefined) edit = false; - if (disable == undefined) disable = false; - options.components = [ - new MessageActionRow().addComponents( - new MessageButton({ - style: Constants.MessageButtonStyles.PRIMARY, - customId: 'paginate__stop', - emoji: paginateEmojis.stop, - disabled: disable - }) - ) - ]; - if (edit) { - options.reply = undefined; - } - } - } - - /** * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long. * * Embed Description Limit = 4096 characters * * Embed Field Limit = 1024 characters */ public async codeblock(code: string, length: number, language?: CodeBlockLang, substr = false): Promise<string> { let hasteOut = ''; - code = util.discord.escapeCodeBlock(code); + code = this.discord.escapeCodeBlock(code); const prefix = `\`\`\`${language}\n`; const suffix = '\n```'; language = language ?? 'txt'; @@ -887,7 +249,12 @@ export class BushClientUtil extends ClientUtil { return code3; } - public inspect(code: any, options?: BushInspectOptions): string { + /** + * Uses {@link inspect} with custom defaults + * @param object - The object you would like to inspect + * @param options - The options you would like to use to inspect the object + */ + public inspect(object: any, options?: BushInspectOptions): string { const { showHidden: _showHidden = false, depth: _depth = 2, @@ -914,7 +281,7 @@ export class BushClientUtil extends ClientUtil { sorted: _sorted, getters: _getters }; - return inspect(code, optionsWithDefaults); + return inspect(object, optionsWithDefaults); } #mapCredential(old: string): string { @@ -931,6 +298,7 @@ export class BushClientUtil extends ClientUtil { /** * Redacts credentials from a string + * @param text - The text to redact credentials from */ public redact(text: string) { for (const credentialName in { ...client.config.credentials, dbPassword: client.config.db.password }) { @@ -1028,7 +396,7 @@ export class BushClientUtil extends ClientUtil { const newValue = this.addOrRemoveFromArray(action, oldValue, value); row[key] = newValue; client.cache.global[key] = newValue; - return await row.save().catch((e) => util.handleError('insertOrRemoveFromGlobal', e)); + return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e)); } /** @@ -1233,136 +601,108 @@ export class BushClientUtil extends ClientUtil { return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, ''); } - get arg() { - return class Arg { - /** - * Casts a phrase to this argument's type. - * @param type - The type to cast to. - * @param resolver - The type resolver. - * @param message - Message that called the command. - * @param phrase - Phrase to process. - */ - public static cast(type: BushArgumentType, resolver: TypeResolver, message: Message, phrase: string): Promise<any> { - return Argument.cast(type, resolver, message, phrase); - } + public async uploadImageToImgur(image: string) { + const clientId = this.client.config.credentials.imgurClientId; - /** - * Creates a type that is the left-to-right composition of the given types. - * If any of the types fails, the entire composition fails. - * @param types - Types to use. - */ - public static compose(...types: BushArgumentType[]): ArgumentTypeCaster { - return Argument.compose(...types); - } + const resp = (await got + .post('https://api.imgur.com/3/upload', { + headers: { + // Authorization: `Bearer ${token}`, + Authorization: `Client-ID ${clientId}`, + Accept: 'application/json' + }, + form: { + image: image, + type: 'base64' + }, + followRedirect: true + }) + .json()) as { data: { link: string } }; - /** - * Creates a type that is the left-to-right composition of the given types. - * If any of the types fails, the composition still continues with the failure passed on. - * @param types - Types to use. - */ - public static composeWithFailure(...types: BushArgumentType[]): ArgumentTypeCaster { - return Argument.composeWithFailure(...types); - } + return resp.data.link; + } - /** - * Checks if something is null, undefined, or a fail flag. - * @param value - Value to check. - */ - public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) { - return Argument.isFailure(value); - } + public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { + const missing = message.member?.permissions.missing(permissions) ?? []; - /** - * Creates a type from multiple types (product type). - * Only inputs where each type resolves with a non-void value are valid. - * @param types - Types to use. - */ - public static product(...types: BushArgumentType[]): ArgumentTypeCaster { - return Argument.product(...types); - } + return missing.length ? missing : null; + } - /** - * Creates a type where the parsed value must be within a range. - * @param type - The type to use. - * @param min - Minimum value. - * @param max - Maximum value. - * @param inclusive - Whether or not to be inclusive on the upper bound. - */ - public static range(type: BushArgumentType, min: number, max: number, inclusive?: boolean): ArgumentTypeCaster { - return Argument.range(type, min, max, inclusive); - } + public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { + const missing = message.guild?.me?.permissions.missing(permissions) ?? []; - /** - * Creates a type that parses as normal but also tags it with some data. - * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. - */ - public static tagged(type: BushArgumentType, tag?: any): ArgumentTypeCaster { - return Argument.tagged(type, tag); - } + return missing.length ? missing : null; + } - /** - * Creates a type from multiple types (union type). - * The first type that resolves to a non-void value is used. - * Each type will also be tagged using `tagged` with themselves. - * @param types - Types to use. - */ - public static taggedUnion(...types: BushArgumentType[]): ArgumentTypeCaster { - return Argument.taggedUnion(...types); - } + public clientSendAndPermCheck( + message: BushMessage | BushSlashMessage, + permissions: PermissionResolvable = [], + checkChannel = false + ) { + const missing = []; + const sendPerm = message.channel!.isThread() ? 'SEND_MESSAGES' : 'SEND_MESSAGES_IN_THREADS'; - /** - * Creates a type that parses as normal but also tags it with some data and carries the original input. - * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. - */ - public static taggedWithInput(type: BushArgumentType, tag?: any): ArgumentTypeCaster { - return Argument.taggedWithInput(type, tag); - } + if (!message.guild!.me!.permissionsIn(message.channel!.id!).has(sendPerm)) missing.push(sendPerm); - /** - * Creates a type from multiple types (union type). - * The first type that resolves to a non-void value is used. - * @param types - Types to use. - */ - public static union(...types: BushArgumentType[]): ArgumentTypeCaster { - return Argument.union(...types); - } + missing.push( + ...(checkChannel + ? message.guild!.me!.permissionsIn(message.channel!.id!).missing(permissions) + : this.clientGuildPermCheck(message, permissions) ?? []) + ); - /** - * Creates a type with extra validation. - * If the predicate is not true, the value is considered invalid. - * @param type - The type to use. - * @param predicate - The predicate function. - */ - public static validate(type: BushArgumentType, predicate: ParsedValuePredicate): ArgumentTypeCaster { - return Argument.validate(type, predicate); - } + return missing.length ? missing : null; + } - /** - * Creates a type that parses as normal but also carries the original input. - * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - */ - public static withInput(type: BushArgumentType): ArgumentTypeCaster { - return Argument.withInput(type); - } - }; + public get arg() { + return Arg; + } + + /** + * Formats and escapes content for formatting + */ + public get format() { + return Format; } /** * Discord.js's Util class */ - get discord() { + public get discord() { return DiscordUtil; } /** * discord-akairo's Util class */ - get akairo() { + public get akairo() { return AkairoUtil; } } + +interface hastebinRes { + key: string; +} + +export interface uuidRes { + uuid: string; + username: string; + username_history?: { username: string }[] | null; + textures: { + custom: boolean; + slim: boolean; + skin: { + url: string; + data: string; + }; + raw: { + value: string; + signature: string; + }; + }; + created_at: string; +} + +interface MojangProfile { + username: string; + uuid: string; +} diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 0be128b..22d4aae 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -158,9 +158,9 @@ export interface BushCommandOptions extends Omit<CommandOptions, 'userPermission /** Allow this command to be run in channels that are blacklisted. */ bypassChannelBlacklist?: boolean; /** Permissions required by the client to run this command. */ - clientPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; + clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; /** Permissions required by the user to run this command. */ - userPermissions?: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; + userPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; } export class BushCommand extends Command { diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 8d44cd4..51c2795 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -1,6 +1,6 @@ import { Guild, MessageOptions, UserResolvable } from 'discord.js'; import { RawGuildData } from 'discord.js/typings/rawDataTypes'; -import { Moderation } from '../../common/moderation'; +import { Moderation } from '../../common/Moderation'; import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel } from '../../models/Guild'; import { ModLogType } from '../../models/ModLog'; import { BushClient, BushUserResolvable } from '../discord-akairo/BushClient'; diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index 954342d..77d03b1 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -1,6 +1,6 @@ import { GuildMember, MessageEmbed, Partialize, Role } from 'discord.js'; import { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; -import { Moderation } from '../../common/moderation'; +import { Moderation } from '../../common/Moderation'; import { ModLogType } from '../../models/ModLog'; import { BushClient } from '../discord-akairo/BushClient'; import { BushGuild } from './BushGuild'; diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index d23bb0f..7b51e61 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -1,6 +1,6 @@ import { Snowflake } from 'discord.js'; import { DataTypes, Sequelize } from 'sequelize'; -import { BadWords } from '../common/autoMod'; +import { BadWords } from '../common/AutoMod'; import { BushClient } from '../extensions/discord-akairo/BushClient'; import { BaseModel } from './BaseModel'; import { jsonArrayInit, jsonParseGet, jsonParseSet, NEVER_USED } from './__helpers'; diff --git a/src/lib/utils/Config.ts b/src/lib/utils/Config.ts index ed73d40..393dd44 100644 --- a/src/lib/utils/Config.ts +++ b/src/lib/utils/Config.ts @@ -1,25 +1,14 @@ import { Snowflake } from 'discord.js'; -export interface ConfigOptions { - credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string }; - environment: 'production' | 'beta' | 'development'; - owners: Snowflake[]; - prefix: string; - channels: { log: Snowflake; error: Snowflake; dm: Snowflake }; - db: { host: string; port: number; username: string; password: string }; - logging: { db: boolean; verbose: boolean; info: boolean }; - supportGuild: { id: Snowflake; invite: string }; -} - export class Config { - public credentials: { token: string; betaToken: string; devToken: string; hypixelApiKey: string; wolframAlphaAppId: string }; - public environment: 'production' | 'beta' | 'development'; + public credentials: Credentials; + public environment: Environment; public owners: Snowflake[]; public prefix: string; - public channels: { log: Snowflake; error: Snowflake; dm: Snowflake }; - public db: { host: string; port: number; username: string; password: string }; - public logging: { db: boolean; verbose: boolean; info: boolean }; - public supportGuild: { id: Snowflake; invite: string }; + public channels: Channels; + public db: DataBase; + public logging: Logging; + public supportGuild: SupportGuild; public constructor(options: ConfigOptions) { this.credentials = options.credentials; @@ -43,10 +32,59 @@ export class Config { public get isProduction(): boolean { return this.environment === 'production'; } + public get isBeta(): boolean { return this.environment === 'beta'; } + public get isDevelopment(): boolean { return this.environment === 'development'; } } + +export interface ConfigOptions { + credentials: Credentials; + environment: Environment; + owners: Snowflake[]; + prefix: string; + channels: Channels; + db: DataBase; + logging: Logging; + supportGuild: SupportGuild; +} + +interface Credentials { + token: string; + betaToken: string; + devToken: string; + hypixelApiKey: string; + wolframAlphaAppId: string; + imgurClientId: string; + imgurClientSecret: string; +} + +type Environment = 'production' | 'beta' | 'development'; + +interface Channels { + log: Snowflake; + error: Snowflake; + dm: Snowflake; +} + +interface DataBase { + host: string; + port: number; + username: string; + password: string; +} + +interface Logging { + db: boolean; + verbose: boolean; + info: boolean; +} + +interface SupportGuild { + id: Snowflake; + invite: string; +} diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts index d2e02ab..e2042d0 100644 --- a/src/listeners/client/interactionCreate.ts +++ b/src/listeners/client/interactionCreate.ts @@ -1,5 +1,5 @@ import { BushButtonInteraction, BushListener } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod'; import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents'; export default class InteractionCreateListener extends BushListener { diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts index b801356..8857aee 100644 --- a/src/listeners/message/automodCreate.ts +++ b/src/listeners/message/automodCreate.ts @@ -1,5 +1,5 @@ import { BushListener } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod'; import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents'; export default class AutomodMessageCreateListener extends BushListener { diff --git a/src/listeners/message/automodUpdate.ts b/src/listeners/message/automodUpdate.ts index 08d4911..9464890 100644 --- a/src/listeners/message/automodUpdate.ts +++ b/src/listeners/message/automodUpdate.ts @@ -1,5 +1,5 @@ import { BushListener, BushMessage } from '@lib'; -import { AutoMod } from '../../lib/common/autoMod'; +import { AutoMod } from '../../lib/common/AutoMod'; import { BushClientEvents } from '../../lib/extensions/discord.js/BushClientEvents'; export default class AutomodMessageUpdateListener extends BushListener { |