diff options
Diffstat (limited to 'src')
21 files changed, 385 insertions, 274 deletions
diff --git a/src/commands/_fake-command/test.ts b/src/commands/_fake-command/ironmoon.ts index 8eeca9e..500b384 100644 --- a/src/commands/_fake-command/test.ts +++ b/src/commands/_fake-command/ironmoon.ts @@ -1,17 +1,19 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -export default class TestCommand extends BushCommand { +export default class IronmoonCommand extends BushCommand { public constructor() { - super('test', { + super('ironmoon', { category: 'fake-commands', description: { content: '', examples: '', usage: '' }, - condition: (message: BushMessage) => { - if (message.content.toLowerCase().includes('ironmoon')) return true; - else return false; - }, completelyHide: true }); } + public condition(message: BushMessage): boolean { + return false; + if (message.content.toLowerCase().includes('ironmoon')) return true; + else return false; + } + public async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { return await message.util.reply('Your message included the word ironmoon.'); } diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index f8ba7e1..af6219b 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -1,118 +1,74 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { exec } from 'child_process'; -import { Constants } from 'discord-akairo'; -import { CommandInteraction, MessageEmbed, MessageEmbedOptions, Util } from 'discord.js'; +import { MessageEmbed as _MessageEmbed, Util } from 'discord.js'; import { transpile } from 'typescript'; -import { inspect, promisify } from 'util'; +import { inspect, InspectOptions, promisify } from 'util'; -const clean = (text) => { - if (typeof text === 'string') { - return Util.cleanCodeBlockContent(text); - } else return text; +const mapCredential = (old: string) => { + const mapping = { + ['token']: 'Main Token', + ['devToken']: 'Dev Token', + ['betaToken']: 'Beta Token', + ['hypixelApiKey']: 'Hypixel Api Key' + }; + return mapping[old] || old; +}; +const redact = (text: string) => { + for (const credentialName in client.config.credentials) { + const credential = client.config.credentials[credentialName]; + const replacement = mapCredential(credentialName); + const escapeRegex = /[.*+?^${}()|[\]\\]/g; + text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`); + text = text.replace( + new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'), + `[${replacement} Omitted]` + ); + } + return text; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const inspectCleanRedactCodeblock = async (input: any, language: 'ts' | 'js', inspectOptions?: InspectOptions) => { + input = typeof input !== 'string' && inspectOptions !== undefined ? inspect(input, inspectOptions) : input; + input = Util.cleanCodeBlockContent(input); + input = redact(input); + return client.util.codeblock(input, 1024, language); }; export default class EvalCommand extends BushCommand { public constructor() { super('eval', { - aliases: ['eval', 'ev'], + aliases: ['eval', 'ev', 'evaluate'], category: 'dev', description: { content: 'Evaluate code.', - usage: 'eval [--depth #] <code> [--sudo] [--silent] [--delete] [--proto] [--hidden] [--ts]', - examples: ['eval message.guild.name', 'eval this.client.ownerID'] + usage: 'eval <code> [--depth #] [--sudo] [--silent] [--delete] [--proto] [--hidden] [--ts]', + examples: ['eval message.channel.delete()'] }, args: [ - { - id: 'sel_depth', - match: Constants.ArgumentMatches.OPTION, - type: Constants.ArgumentTypes.NUMBER, - flag: '--depth', - default: 0 - }, - { - id: 'sudo', - match: Constants.ArgumentMatches.FLAG, - flag: '--sudo' - }, - { - id: 'delete_msg', - match: Constants.ArgumentMatches.FLAG, - flag: '--delete' - }, - { - id: 'silent', - match: Constants.ArgumentMatches.FLAG, - flag: '--silent' - }, - { - id: 'typescript', - match: Constants.ArgumentMatches.FLAG, - flag: '--ts' - }, - { - id: 'hidden', - match: Constants.ArgumentMatches.FLAG, - flag: '--hidden' - }, - { - id: 'show_proto', - match: Constants.ArgumentMatches.FLAG, - flag: '--proto' - }, + { id: 'sel_depth', match: 'option', type: 'integer', flag: '--depth', default: 0 }, + { id: 'sudo', match: 'flag', flag: '--sudo' }, + { id: 'delete_msg', match: 'flag', flag: '--delete' }, + { id: 'silent', match: 'flag', flag: '--silent' }, + { id: 'typescript', match: 'flag', flag: '--ts' }, + { id: 'hidden', match: 'flag', flag: '--hidden' }, + { id: 'show_proto', match: 'flag', flag: '--proto' }, { id: 'code', - match: Constants.ArgumentMatches.REST, - type: Constants.ArgumentTypes.STRING, - prompt: { - start: 'What would you like to eval?', - retry: '{error} Invalid code to eval.' - } + match: 'rest', + type: 'string', + prompt: { start: 'What would you like to eval?', retry: '{error} Invalid code to eval.' } } ], slash: true, slashOptions: [ - { - name: 'code', - description: 'The code you would like to evaluate.', - type: 'STRING', - required: true - }, - { - name: 'sel_depth', - description: 'How deep to display the output.', - type: 'INTEGER', - required: false - }, - { - name: 'sudo', - description: 'Whether or not to override checks.', - type: 'BOOLEAN', - required: false - }, - { - name: 'silent', - description: 'Whether or not to make the response silent', - type: 'BOOLEAN', - required: false - }, - { - name: 'typescript', - description: 'Whether or not to compile the code from typescript.', - type: 'BOOLEAN', - required: false - }, - { - name: 'hidden', - description: 'Whether or not to show hidden items.', - type: 'BOOLEAN', - required: false - }, - { - name: 'show_proto', - description: 'Show prototype.', - type: 'BOOLEAN', - required: false - } + { name: 'code', description: 'The code you would like to evaluate.', type: 'STRING', required: true }, + { name: 'sel_depth', description: 'How deep to display the output.', type: 'INTEGER', required: false }, + { name: 'sudo', description: 'Whether or not to override checks.', type: 'BOOLEAN', required: false }, + { name: 'silent', description: 'Whether or not to make the response silent', type: 'BOOLEAN', required: false }, + { name: 'typescript', description: 'Whether or not the code is typescript.', type: 'BOOLEAN', required: false }, + { name: 'hidden', description: 'Whether or not to show hidden items.', type: 'BOOLEAN', required: false }, + { name: 'show_proto', description: 'Show prototype.', type: 'BOOLEAN', required: false } ], ownerOnly: true }); @@ -136,153 +92,98 @@ export default class EvalCommand extends BushCommand { if (message.util.isSlash) { await (message as BushSlashMessage).interaction.defer({ ephemeral: args.silent }); } - const code: { js?: string | null; ts?: string | null; lang?: 'js' | 'ts' } = {}; - args.code = args.code.replace(/[“”]/g, '"'); - args.code = args.code.replace(/```*(?:js|ts)?/g, ''); - if (args.typescript) { - code.ts = args.code; - code.js = transpile(args.code); - code.lang = 'ts'; - } else { - code.ts = null; - code.js = args.code; - code.lang = 'js'; - } + args.code = args.code.replace(/[“”]/g, '"').replace(/```*(?:js|ts)?/g, ''); - const embed: MessageEmbed = new MessageEmbed(); - const bad_phrases: string[] = ['delete', 'destroy']; + const code = { + ts: args.typescript ? args.code : null, + js: args.typescript ? transpile(args.code) : args.code, + lang: args.typescript ? 'ts' : 'js' + }; - function ae(old: string) { - const mapping = { - ['token']: 'Main Token', - ['devToken']: 'Dev Token', - ['betaToken']: 'Beta Token', - ['hypixelApiKey']: 'Hypixel Api Key' - }; - return mapping[old] || old; - } + const embed = new _MessageEmbed(); + const badPhrases = ['delete', 'destroy']; - if (bad_phrases.some((p) => code[code.lang].includes(p)) && !args.sudo) { + if (badPhrases.some((p) => code[code.lang].includes(p)) && !args.sudo) { return await message.util.send(`${this.client.util.emojis.error} This eval was blocked by smooth brain protection™.`); } - const embeds: (MessageEmbed | MessageEmbedOptions)[] = [new MessageEmbed()]; - embeds.some((embed) => embed); - try { - let output; - /* eslint-disable @typescript-eslint/no-unused-vars */ - const sh = promisify(exec), - me = message.member, - member = message.member, - bot = this.client, - guild = message.guild, - channel = message.channel, - config = this.client.config, - members = message.guild?.members, - roles = message.guild?.roles, - client = this.client, - { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), - { - ButtonInteraction, - Collector, - CommandInteraction, - Interaction, - Message, - MessageActionRow, - MessageAttachment, - MessageButton, - MessageCollector, - InteractionCollector, - MessageEmbed, - MessageSelectMenu, - ReactionCollector, - Util, - Collection - } = await import('discord.js'), - { Canvas } = await import('node-canvas'); - /* eslint-enable @typescript-eslint/no-unused-vars */ - if (code[code.lang].replace(/ /g, '').includes('9+10' || '10+9')) { - output = 21; - } else { - output = eval(code.js); - output = await output; - } - let proto, outputProto; - if (args.show_proto) { - proto = Object.getPrototypeOf(output); - outputProto = clean(inspect(proto, { depth: 1, getters: true, showHidden: true })); - } - if (typeof output !== 'string') - output = inspect(output, { depth: args.sel_depth || 0, showHidden: args.hidden, getters: true, showProxy: true }); - for (const credentialName in this.client.config.credentials) { - const credential = this.client.config.credentials[credentialName]; - const newCredential = ae(credentialName); - output = output.replace( - new RegExp(credential.toString().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), - `[${newCredential} Omitted]` - ); - output = output.replace( - new RegExp( - [...credential.toString()] - .reverse() - .join('') - .replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), - 'g' - ), - `[${newCredential} Omitted]` - ); - } + /* eslint-disable @typescript-eslint/no-unused-vars */ + const sh = promisify(exec), + me = message.member, + member = message.member, + bot = this.client, + guild = message.guild, + channel = message.channel, + config = this.client.config, + members = message.guild?.members, + roles = message.guild?.roles, + client = this.client, + emojis = this.client.util.emojis, + colors = this.client.util.colors, + util = this.client.util, + { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), + { + ButtonInteraction, + Collector, + CommandInteraction, + Interaction, + Message, + MessageActionRow, + MessageAttachment, + MessageButton, + MessageCollector, + InteractionCollector, + MessageEmbed, + MessageSelectMenu, + ReactionCollector, + Util, + Collection + } = await import('discord.js'), + { Canvas } = await import('node-canvas'); + /* eslint-enable @typescript-eslint/no-unused-vars */ - output = clean(output); - const inputJS = clean(code.js); + const inputJS = await inspectCleanRedactCodeblock(code.js, 'js'); + const inputTS = code.lang === 'ts' ? await inspectCleanRedactCodeblock(code.ts, 'ts') : undefined; + try { + const rawOutput = code[code.lang].replace(/ /g, '').includes('9+10' || '10+9') ? '21' : await eval(code.js); + const output = await inspectCleanRedactCodeblock(rawOutput, 'js', { + depth: args.sel_depth || 0, + showHidden: args.hidden, + getters: true, + showProxy: true + }); + const proto = args.show_proto + ? await inspectCleanRedactCodeblock(Object.getPrototypeOf(rawOutput), 'js', { + depth: 1, + getters: true, + showHidden: true + }) + : undefined; - embed - .setTitle(`${this.client.util.emojis.successFull} Evaled code successfully`) - .setColor(this.client.util.colors.success) - .setFooter(message.author.tag, message.author.displayAvatarURL({ dynamic: true })) - .setTimestamp(); - if (code.lang === 'ts') { - const inputTS = clean(code.ts); - embed - .addField('📥 Input (typescript)', await this.client.util.codeblock(inputTS, 1024, 'ts')) - .addField('📥 Input (transpiled javascript)', await this.client.util.codeblock(inputJS, 1024, 'js')); - } else { - embed.addField('📥 Input', await this.client.util.codeblock(inputJS, 1024, 'js')); - } - embed.addField('📤 Output', await this.client.util.codeblock(output, 1024, 'js')); - if (args.show_proto) embed.addField('⚙️ Proto', await this.client.util.codeblock(outputProto, 1024, 'js')); + embed.setTitle(`${emojis.successFull} Evaluated code successfully`).setColor(colors.success); + if (inputTS) embed.addField('📥 Input (typescript)', inputTS).addField('📥 Input (transpiled javascript)', inputJS); + else embed.addField('📥 Input', inputJS); + embed.addField('📤 Output', output); + if (proto) embed.addField('⚙️ Proto', proto); } catch (e) { - const inputJS = clean(code.js); - embed - .setTitle(`${this.client.util.emojis.errorFull} Code was not able to be evaled.`) - .setColor(this.client.util.colors.error) - .setFooter(message.author.tag, message.author.displayAvatarURL({ dynamic: true })) - .setTimestamp(); - if (code.lang === 'ts') { - const inputTS = clean(code.ts); - embed - .addField('📥 Input (typescript)', await this.client.util.codeblock(inputTS, 1024, 'ts')) - .addField('📥 Input (transpiled javascript)', await this.client.util.codeblock(inputJS, 1024, 'js')); - } else { - embed.addField('📥 Input', await this.client.util.codeblock(inputJS, 1024, 'js')); - } - embed.addField('📤 Output', await this.client.util.codeblock(e?.stack || e, 1024, 'js')); + embed.setTitle(`${emojis.errorFull} Code was not able to be evaluated.`).setColor(colors.error); + if (inputTS) embed.addField('📥 Input (typescript)', inputTS).addField('📥 Input (transpiled javascript)', inputJS); + else embed.addField('📥 Input', inputJS); + embed.addField('📤 Output', await inspectCleanRedactCodeblock(e?.stack || e, 'js')); } - if (!args.silent && !message.util.isSlash) { - await message.util.reply({ embeds: [embed], ephemeral: args.silent }); - } else if (message.util.isSlash) { - await (message.interaction as CommandInteraction).editReply({ embeds: [embed] }); + + embed.setTimestamp().setFooter(message.author.tag, message.author.displayAvatarURL({ dynamic: true })); + + if (!args.silent || message.util.isSlash) { + await message.util.reply({ embeds: [embed] }); } else { try { await message.author.send({ embeds: [embed] }); - if (!args.deleteMSG) await (message as BushMessage).react(this.client.util.emojis.successFull); + if (!args.deleteMSG) await (message as BushMessage).react(emojis.successFull); } catch { - if (!args.deleteMSG) await (message as BushMessage).react(this.client.util.emojis.errorFull); + if (!args.deleteMSG) await (message as BushMessage).react(emojis.errorFull); } } - - if (args.deleteMSG && (message as BushMessage).deletable) { - await (message as BushMessage).delete(); - } + if (args.deleteMSG && (message as BushMessage).deletable) await (message as BushMessage).delete(); } } diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts index 9dc2632..9f16e9c 100644 --- a/src/commands/dev/say.ts +++ b/src/commands/dev/say.ts @@ -16,19 +16,10 @@ export default class SayCommand extends BushCommand { id: 'say', type: 'string', match: 'rest', - prompt: { - start: 'What would you like the bot to say?', - retry: '{error} Choose something valid to say.' - } - } - ], - slashOptions: [ - { - name: 'content', - description: 'What would you like the bot to say?', - type: 'STRING' + prompt: { start: 'What would you like the bot to say?', retry: '{error} Choose something valid to say.' } } ], + slashOptions: [{ name: 'content', description: 'What would you like the bot to say?', type: 'STRING' }], ownerOnly: true, clientPermissions: ['SEND_MESSAGES'], slash: true diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts new file mode 100644 index 0000000..49a3133 --- /dev/null +++ b/src/commands/dev/test.ts @@ -0,0 +1,154 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { BushCommand, BushMessage } from '@lib'; +import { Constants } from 'discord-akairo'; +import { Constants as jsConstants, MessageActionRow, MessageButton, MessageEmbed } from 'discord.js'; + +export default class TestCommand extends BushCommand { + public constructor() { + super('test', { + aliases: ['test'], + category: 'dev', + description: { + content: 'A command to stuff.', + usage: 'test [feature]', + examples: ['test lots of buttons', 'test buttons'] + }, + clientPermissions: ['SEND_MESSAGES'], + userPermissions: ['SEND_MESSAGES'], + args: [ + { + id: 'feature', + type: Constants.ArgumentTypes.STRING, + match: Constants.ArgumentMatches.REST, + prompt: { + start: 'start prompt', + retry: 'retry prompt', + optional: true + } + } + ] + }); + } + + // eslint-disable-next-line require-await + public async exec(message: BushMessage, args: { feature: string }): Promise<unknown> { + const responses = [ + 'Yes master.', + 'Test it your self bitch, I am hungry.', + 'Give me a break.', + 'I am not your slave.', + 'I have done as you wished, now please feed me.', + `Someone help me I am trapped in ${message.author.username}'s basement.` + ]; + if (!message.author.isOwner()) { + return await message.util.reply(responses[Math.floor(Math.random() * responses.length)]); + } + + const s = jsConstants.MessageButtonStyles; + if (['button', 'buttons'].includes(args?.feature?.toLowerCase())) { + const ButtonRow = new MessageActionRow().addComponents( + new MessageButton({ style: s.PRIMARY, customId: 'primaryButton', label: 'Primary' }), + new MessageButton({ style: s.SECONDARY, customId: 'secondaryButton', label: 'Secondary' }), + new MessageButton({ style: s.SUCCESS, customId: 'success', label: 'Success' }), + new MessageButton({ style: s.DANGER, customId: 'danger', label: 'Danger' }), + new MessageButton({ style: s.LINK, label: 'Link', url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' }) + ); + return await message.util.reply({ content: 'buttons', components: [ButtonRow] }); + } else if (['embed', 'button embed'].includes(args?.feature?.toLowerCase())) { + const embed = new MessageEmbed() + .addField('Field Name', 'Field Content') + .setAuthor('Author', 'https://www.w3schools.com/w3css/img_snowtops.jpg', 'https://google.com/') + .setColor(message.member.displayColor) + .setDescription('Description') + .setFooter('Footer', message.author.avatarURL()) + .setURL('https://duckduckgo.com/') + .setTimestamp() + .setImage('https://media.sproutsocial.com/uploads/2017/02/10x-featured-social-media-image-size.png') + .setThumbnail( + 'https://images.unsplash.com/photo-1501183007986-d0d080b147f9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2134&q=80' + ) + .setTitle('Title'); + + const buttonRow = new MessageActionRow().addComponents( + new MessageButton({ style: jsConstants.MessageButtonStyles.LINK, label: 'Link', url: 'https://www.google.com/' }) + ); + return await message.util.reply({ content: 'Test', embeds: [embed], components: [buttonRow] }); + } else if (['lots of buttons'].includes(args?.feature?.toLowerCase())) { + const ButtonRows: MessageActionRow[] = []; + for (let a = 1; a <= 5; a++) { + const row = new MessageActionRow(); + for (let b = 1; b <= 5; b++) { + const id = (a + 5 * (b - 1)).toString(); + const button = new MessageButton({ style: jsConstants.MessageButtonStyles.SECONDARY, customId: id, label: id }); + row.addComponents(button); + } + ButtonRows.push(row); + } + return await message.util.reply({ content: 'buttons', components: ButtonRows }); + } else if (['paginate'].includes(args?.feature?.toLowerCase())) { + const embeds = []; + for (let i = 1; i <= 5; i++) { + embeds.push(new MessageEmbed().setDescription(i.toString())); + } + return await this.client.util.buttonPaginate(message, embeds); + } else if (['lots of embeds'].includes(args?.feature?.toLowerCase())) { + const description = 'This is a description.'; + const author = { name: 'This is a author', iconURL: message.author.avatarURL({ dynamic: true }) }; + const footer = { text: 'This is a footer', iconURL: message.author.avatarURL({ dynamic: true }) }; + const fields = []; + for (let i = 0; i < 25; i++) { + fields.push({ name: 'Field ' + i, value: 'Field Value ' + i }); + } + const c = this.client.util.colors; + const o = { description, author, footer, fields }; + + const embeds = [ + new MessageEmbed({ ...o, ...{ title: 'Embed Title 0', color: c.red } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 1', color: c.orange } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 2', color: c.gold } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 3', color: c.yellow } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 4', color: c.green } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 5', color: c.darkGreen } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 6', color: c.aqua } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 7', color: c.blue } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 8', color: c.purple } }).setTimestamp(), + new MessageEmbed({ ...o, ...{ title: 'Embed Title 9', color: c.pink } }).setTimestamp() + ]; + + const ButtonRows: MessageActionRow[] = []; + for (let a = 1; a <= 5; a++) { + const row = new MessageActionRow(); + for (let b = 1; b <= 5; b++) { + const id = (a + 5 * (b - 1)).toString(); + const button = new MessageButton({ style: jsConstants.MessageButtonStyles.SECONDARY, customId: id, label: id }); + row.addComponents(button); + } + ButtonRows.push(row); + } + return await message.util.reply({ content: 'this is content', components: ButtonRows, embeds }); + } else if (['delete slash commands'].includes(args?.feature?.toLowerCase())) { + // let guildCommandCount = 0; + // this.client.guilds.cache.forEach(guild => + // guild.commands.fetch().then(commands => { + // commands.forEach(async command => { + // await command.delete(); + // guildCommandCount++; + // }); + // }) + // ); + const guildCommands = await message.guild.commands.fetch(); + guildCommands.forEach(async (command) => await command.delete()); + const globalCommands = await this.client.application.commands.fetch(); + globalCommands.forEach(async (command) => await command.delete()); + + return await message.util.reply( + `${this.client.util.emojis.success} Removed **${/* guildCommandCount */ guildCommands.size}** guild commands and **${ + globalCommands.size + }** global commands.` + ); + } else if (['drop down', 'drop downs', 'select menu', 'select menus'].includes(args?.feature?.toLowerCase())) { + + } + return await message.util.reply(responses[Math.floor(Math.random() * responses.length)]); + } +} diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 8496d2a..3caae38 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -3,13 +3,15 @@ import { AkairoClient } from 'discord-akairo'; import { Guild, Intents, + InteractionReplyOptions, Message, MessageEditOptions, MessageOptions, MessagePayload, ReplyMessageOptions, Snowflake, - Structures + Structures, + WebhookEditMessageOptions } from 'discord.js'; import * as path from 'path'; import { exit } from 'process'; @@ -57,6 +59,8 @@ import { BushTaskHandler } from './BushTaskHandler'; export type BushReplyMessageType = string | MessagePayload | ReplyMessageOptions; export type BushEditMessageType = string | MessageEditOptions | MessagePayload; +export type BushSlashSendMessageType = string | MessagePayload | InteractionReplyOptions +export type BushSlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions export type BushSendMessageType = string | MessagePayload | MessageOptions; export type BushThreadMemberResolvable = BushThreadMember | BushUserResolvable; export type BushUserResolvable = BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember; diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 306e049..3a50480 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -31,12 +31,11 @@ import { MessageEditOptions, MessageEmbed, MessageOptions, - MessagePayload, + Snowflake, TextChannel, User, - Util, - WebhookEditMessageOptions + Util } from 'discord.js'; import got from 'got'; import humanizeDuration from 'humanize-duration'; @@ -44,7 +43,7 @@ import { promisify } from 'util'; import { ActivePunishment, ActivePunishmentType } from '../../models/ActivePunishment'; import { BushNewsChannel } from '../discord.js/BushNewsChannel'; import { BushTextChannel } from '../discord.js/BushTextChannel'; -import { BushUserResolvable } from './BushClient'; +import { BushSlashEditMessageType, BushSlashSendMessageType, BushUserResolvable } from './BushClient'; interface hastebinRes { key: string; @@ -458,9 +457,9 @@ export class BushClientUtil extends ClientUtil { let hasteOut = ''; const tildes = '```'; const formattingLength = 2 * tildes.length + language.length + 2 * '\n'.length; - if (code.length + formattingLength > length) hasteOut = 'Too large to display. Hastebin: ' + (await this.haste(code)); + if (code.length + formattingLength >= length) hasteOut = 'Too large to display. Hastebin: ' + (await this.haste(code)); - const code2 = code.length > length ? code.substring(0, length - (hasteOut.length + '\n'.length + formattingLength)) : code; + const code2 = hasteOut ? code.substring(0, length - (hasteOut.length + '\n'.length + formattingLength)) : code; return ( tildes + language + '\n' + Util.cleanCodeBlockContent(code2) + '\n' + tildes + (hasteOut.length ? '\n' + hasteOut : '') ); @@ -468,9 +467,9 @@ export class BushClientUtil extends ClientUtil { public async slashRespond( interaction: CommandInteraction, - responseOptions: string | MessagePayload | WebhookEditMessageOptions + responseOptions: BushSlashSendMessageType|BushSlashEditMessageType ): Promise<Message | APIMessage> { - let newResponseOptions: string | MessagePayload | WebhookEditMessageOptions = {}; + let newResponseOptions: BushSlashSendMessageType|BushSlashEditMessageType = {}; if (typeof responseOptions === 'string') { newResponseOptions.content = responseOptions; } else { @@ -751,4 +750,9 @@ export class BushClientUtil extends ClientUtil { /* eslint-disable @typescript-eslint/no-unused-vars */ public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) {} /* eslint-enable @typescript-eslint/no-unused-vars */ + + public async automod(message: BushMessage) { + const autoModPhases = await message.guild.getSetting('autoModPhases'); + if (autoModPhases.includes(message.content.toString()) && message.deletable) message.delete() + } } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 6cf981b..0127a59 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -44,6 +44,10 @@ type BushArgumentType = | 'newsChannels' | 'storeChannel' | 'storeChannels' + | 'stageChannel' + | 'stageChannels' + | 'threadChannel' + | 'threadChannels' | 'role' | 'roles' | 'emoji' @@ -63,17 +67,19 @@ type BushArgumentType = | 'command' | 'inhibitor' | 'listener' - | 'duration' - | (string | string[])[] - | RegExp - | string; + | 'duration'; -export interface BushArgumentOptions extends ArgumentOptions { - type?: BushArgumentType | ArgumentTypeCaster; +interface BaseBushArgumentOptions extends ArgumentOptions { id: string; description?: string; prompt?: ArgumentPromptOptions; } +export interface BushArgumentOptions extends BaseBushArgumentOptions { + type?: BushArgumentType; +} +export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { + type?: ArgumentTypeCaster | (string | string[])[] | RegExp | string; +} export interface BushCommandOptions extends CommandOptions { hidden?: boolean; @@ -84,7 +90,7 @@ export interface BushCommandOptions extends CommandOptions { usage: string | string[]; examples: string | string[]; }; - args?: BushArgumentOptions[] | ArgumentGenerator; + args?: BushArgumentOptions[] | CustomBushArgumentOptions[] | ArgumentGenerator; category: string; completelyHide?: boolean; } @@ -110,6 +116,7 @@ export class BushCommand extends Command { public constructor(id: string, options?: BushCommandOptions) { super(id, options); + options.category; this.options = options; this.hidden = options.hidden || false; this.restrictedChannels = options.restrictedChannels; diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index f8ebe46..3972259 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -14,6 +14,7 @@ export interface GuildModel { punishmentEnding: string; disabledCommands: string[]; lockdownChannels: Snowflake[]; + autoModPhases: string[] } export interface GuildModelCreationAttributes { @@ -27,6 +28,7 @@ export interface GuildModelCreationAttributes { punishmentEnding?: string; disabledCommands?: string[]; lockdownChannels?: Snowflake[]; + autoModPhases?: string[] } export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel { @@ -40,6 +42,7 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i punishmentEnding: string; disabledCommands: string[]; lockdownChannels: Snowflake[]; + autoModPhases: string[] static initModel(sequelize: Sequelize, client: BushClient): void { Guild.init( @@ -119,6 +122,17 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i }, allowNull: false, defaultValue: '[]' + }, + autoModPhases: { + type: DataTypes.TEXT, + get: function () { + return JSON.parse(this.getDataValue('autoModPhases') as unknown as string); + }, + set: function (val: string[]) { + return this.setDataValue('autoModPhases', JSON.stringify(val) as unknown as string[]); + }, + allowNull: false, + defaultValue: '[]' } }, { sequelize: sequelize } diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts index 0e77fea..0503f2e 100644 --- a/src/listeners/client/interactionCreate.ts +++ b/src/listeners/client/interactionCreate.ts @@ -10,7 +10,7 @@ export default class InteractionCreateListener extends BushListener { }); } - async exec([interaction]: ClientEvents['interactionCreate']): Promise<unknown> { + async exec(...[interaction]: ClientEvents['interactionCreate']): Promise<unknown> { if (!interaction) return; if (interaction.isCommand()) { this.client.console.info( diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts index c02f21c..e75610b 100644 --- a/src/listeners/commands/commandBlocked.ts +++ b/src/listeners/commands/commandBlocked.ts @@ -8,7 +8,7 @@ export default class CommandBlockedListener extends BushListener { }); } - public async exec([message, command, reason]: BushCommandHandlerEvents['commandBlocked']): Promise<unknown> { + public async exec(...[message, command, reason]: BushCommandHandlerEvents['commandBlocked']): Promise<unknown> { this.client.console.info( 'CommandBlocked', `<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`, diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 1aaefad..6de764c 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -10,7 +10,7 @@ export default class CommandErrorListener extends BushListener { }); } - public async exec([error, message, command]: BushCommandHandlerEvents['error']): Promise<void> { + public async exec(...[error, message, command]: BushCommandHandlerEvents['error']): Promise<void> { const errorNo = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number const errorEmbed: MessageEmbed = new MessageEmbed() .setTitle(`Error # \`${errorNo}\`: An error occurred`) diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts index 765bc72..e06f8f8 100644 --- a/src/listeners/commands/commandMissingPermissions.ts +++ b/src/listeners/commands/commandMissingPermissions.ts @@ -9,7 +9,7 @@ export default class CommandMissingPermissionsListener extends BushListener { }); } - public async exec([message, command, type, missing]: BushCommandHandlerEvents['missingPermissions']): Promise<void> { + public async exec(...[message, command, type, missing]: BushCommandHandlerEvents['missingPermissions']): Promise<void> { const niceMissing = []; missing.forEach((missing) => { if (this.client.consts.mappings.permissions[missing]) { diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts index dcc1d83..421da02 100644 --- a/src/listeners/commands/commandStarted.ts +++ b/src/listeners/commands/commandStarted.ts @@ -8,7 +8,7 @@ export default class CommandStartedListener extends BushListener { category: 'commands' }); } - exec([message, command]: BushCommandHandlerEvents['commandStarted']): void { + exec(...[message, command]: BushCommandHandlerEvents['commandStarted']): void { this.client.logger.info( 'Command', `The <<${command.id}>> command was used by <<${message.author.tag}>> in ${ diff --git a/src/listeners/commands/slashBlocked.ts b/src/listeners/commands/slashBlocked.ts index ecbe863..e59235e 100644 --- a/src/listeners/commands/slashBlocked.ts +++ b/src/listeners/commands/slashBlocked.ts @@ -9,7 +9,7 @@ export default class SlashBlockedListener extends BushListener { }); } - public async exec([message, command, reason]: BushCommandHandlerEvents['slashBlocked']): Promise<unknown> { + public async exec(...[message, command, reason]: BushCommandHandlerEvents['slashBlocked']): Promise<unknown> { this.client.console.info( 'SlashBlocked', `<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`, diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts index 4eaf293..03ba899 100644 --- a/src/listeners/commands/slashCommandError.ts +++ b/src/listeners/commands/slashCommandError.ts @@ -10,7 +10,7 @@ export default class SlashCommandErrorListener extends BushListener { category: 'commands' }); } - async exec([error, message, command]: BushCommandHandlerEvents['slashError']): Promise<void> { + async exec(...[error, message, command]: BushCommandHandlerEvents['slashError']): Promise<void> { const errorNo = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number const errorEmbed: MessageEmbed = new MessageEmbed() .setTitle(`Slash Error # \`${errorNo}\`: An error occurred`) diff --git a/src/listeners/commands/slashMissingPermissions.ts b/src/listeners/commands/slashMissingPermissions.ts index 45966bb..a41dd8f 100644 --- a/src/listeners/commands/slashMissingPermissions.ts +++ b/src/listeners/commands/slashMissingPermissions.ts @@ -9,7 +9,7 @@ export default class SlashMissingPermissionsListener extends BushListener { }); } - public async exec([message, command, type, missing]: BushCommandHandlerEvents['slashMissingPermissions']): Promise<void> { + public async exec(...[message, command, type, missing]: BushCommandHandlerEvents['slashMissingPermissions']): Promise<void> { const niceMissing = []; missing.forEach((missing) => { if (this.client.consts.mappings.permissions[missing]) { diff --git a/src/listeners/commands/slashStarted.ts b/src/listeners/commands/slashStarted.ts index f49f5dd..2a89b03 100644 --- a/src/listeners/commands/slashStarted.ts +++ b/src/listeners/commands/slashStarted.ts @@ -8,7 +8,7 @@ export default class SlashStartedListener extends BushListener { category: 'commands' }); } - async exec([message, command]: BushCommandHandlerEvents['slashStarted']): Promise<unknown> { + async exec(...[message, command]: BushCommandHandlerEvents['slashStarted']): Promise<unknown> { return await this.client.logger.info( 'SlashCommand', `The <<${command.id}>> command was used by <<${message.author.tag}>> in ${ diff --git a/src/listeners/guild/syncUnban.ts b/src/listeners/guild/syncUnban.ts index c9ba0cb..389aae6 100644 --- a/src/listeners/guild/syncUnban.ts +++ b/src/listeners/guild/syncUnban.ts @@ -9,7 +9,7 @@ export default class SyncUnbanListener extends BushListener { }); } - public async exec([ban]: ClientEvents['guildBanRemove']): Promise<void> { + public async exec(...[ban]: ClientEvents['guildBanRemove']): Promise<void> { const bans = await ActivePunishment.findAll({ where: { user: ban.user, diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts new file mode 100644 index 0000000..f1c4ccd --- /dev/null +++ b/src/listeners/message/automodCreate.ts @@ -0,0 +1,16 @@ +import { BushListener, BushMessage } from '@lib'; +import { ClientEvents } from 'discord.js'; + +export default class AutomodMessageCreateListener extends BushListener { + public constructor() { + super('automodCreate', { + emitter: 'client', + event: 'messageCreate', + category: 'message' + }); + } + + async exec(...[message]: ClientEvents['messageCreate']): Promise<void> { + return await this.client.util.automod(message as BushMessage) + } +} diff --git a/src/listeners/message/automodUpdate.ts b/src/listeners/message/automodUpdate.ts new file mode 100644 index 0000000..37e00bf --- /dev/null +++ b/src/listeners/message/automodUpdate.ts @@ -0,0 +1,17 @@ +import { BushListener, BushMessage } from '@lib'; +import { ClientEvents, Message } from 'discord.js'; + +export default class AutomodMessageUpdateListener extends BushListener { + public constructor() { + super('automodUpdate', { + emitter: 'client', + event: 'messageUpdate', + category: 'message' + }); + } + + async exec(...[message]: ClientEvents['messageUpdate']): Promise<void> { + const fullMessage = message.partial ? await message.fetch() : (message as Message); + return await this.client.util.automod(fullMessage as BushMessage); + } +} diff --git a/src/listeners/message/level.ts b/src/listeners/message/level.ts index b06fdd2..1ff098e 100644 --- a/src/listeners/message/level.ts +++ b/src/listeners/message/level.ts @@ -1,5 +1,5 @@ -import { BushListener, Level } from '@lib'; -import { Message, MessageType } from 'discord.js'; +import { BushCommandHandlerEvents, BushListener, Level } from '@lib'; +import { MessageType } from 'discord.js'; export default class LevelListener extends BushListener { private levelCooldowns: Set<string> = new Set(); @@ -7,10 +7,11 @@ export default class LevelListener extends BushListener { public constructor() { super('level', { emitter: 'commandHandler', - event: 'messageInvalid' // Using messageInvalid here so commands don't give xp + event: 'messageInvalid', // Using messageInvalid here so commands don't give xp + category: 'message' }); } - async exec(message: Message): Promise<void> { + async exec(...[message]: BushCommandHandlerEvents['messageInvalid']): Promise<void> { if (message.author.bot) return; if (!message.author) return; if (!message.guild) return; |