diff options
| author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2022-08-18 22:42:12 -0400 |
|---|---|---|
| committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2022-08-18 22:42:12 -0400 |
| commit | 2356d2c44736fb83021dacb551625852111c8ce6 (patch) | |
| tree | 10408d22fdd7a358d2f5c5917c3b59e55aa4c19d /src/lib/common | |
| parent | 8aed6f93f7740c592cbc0e2f9fd3269c05286077 (diff) | |
| download | tanzanite-2356d2c44736fb83021dacb551625852111c8ce6.tar.gz tanzanite-2356d2c44736fb83021dacb551625852111c8ce6.tar.bz2 tanzanite-2356d2c44736fb83021dacb551625852111c8ce6.zip | |
restructure, experimental presence and member automod, fixed bugs probably made some more bugs
Diffstat (limited to 'src/lib/common')
| -rw-r--r-- | src/lib/common/AutoMod.ts | 529 | ||||
| -rw-r--r-- | src/lib/common/ButtonPaginator.ts | 219 | ||||
| -rw-r--r-- | src/lib/common/ConfirmationPrompt.ts | 64 | ||||
| -rw-r--r-- | src/lib/common/DeleteButton.ts | 78 | ||||
| -rw-r--r-- | src/lib/common/HighlightManager.ts | 485 | ||||
| -rw-r--r-- | src/lib/common/Sentry.ts | 24 | ||||
| -rw-r--r-- | src/lib/common/tags.ts | 34 | ||||
| -rw-r--r-- | src/lib/common/typings/BushInspectOptions.ts | 123 | ||||
| -rw-r--r-- | src/lib/common/typings/CodeBlockLang.ts | 311 | ||||
| -rw-r--r-- | src/lib/common/util/Arg.ts | 192 | ||||
| -rw-r--r-- | src/lib/common/util/Format.ts | 119 | ||||
| -rw-r--r-- | src/lib/common/util/Minecraft.ts | 349 | ||||
| -rw-r--r-- | src/lib/common/util/Minecraft_Test.ts | 86 | ||||
| -rw-r--r-- | src/lib/common/util/Moderation.ts | 556 |
14 files changed, 0 insertions, 3169 deletions
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts deleted file mode 100644 index 44c6dee..0000000 --- a/src/lib/common/AutoMod.ts +++ /dev/null @@ -1,529 +0,0 @@ -import { colors, emojis, format, formatError, Moderation, unmuteResponse } from '#lib'; -import assert from 'assert/strict'; -import chalk from 'chalk'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - EmbedBuilder, - GuildMember, - PermissionFlagsBits, - type ButtonInteraction, - type Message, - type Snowflake, - type TextChannel -} from 'discord.js'; -import UnmuteCommand from '../../commands/moderation/unmute.js'; - -/** - * Handles auto moderation functionality. - */ -export class AutoMod { - /** - * Whether or not a punishment has already been given to the user - */ - private punished = false; - - /** - * @param message The message to check and potentially perform automod actions to - */ - public constructor(private message: Message) { - if (message.author.id === message.client.user?.id) return; - void this.handle(); - } - - /** - * Whether or not the message author is immune to auto moderation - */ - private get isImmune() { - if (!this.message.inGuild()) return false; - assert(this.message.member); - - if (this.message.author.isOwner()) return true; - if (this.message.guild.ownerId === this.message.author.id) return true; - if (this.message.member.permissions.has('Administrator')) return true; - - return false; - } - - /** - * Handles the auto moderation - */ - private async handle() { - if (!this.message.inGuild()) return; - if (!(await this.message.guild.hasFeature('automod'))) return; - if (this.message.author.bot) return; - - traditional: { - if (this.isImmune) break traditional; - const badLinksArray = this.message.client.utils.getShared('badLinks'); - const badLinksSecretArray = this.message.client.utils.getShared('badLinksSecret'); - const badWordsRaw = this.message.client.utils.getShared('badWords'); - - const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? []; - const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])]; - - const badLinks: BadWordDetails[] = uniqueLinks.map((link) => ({ - match: link, - severity: Severity.PERM_MUTE, - ignoreSpaces: false, - ignoreCapitalization: true, - reason: 'malicious link', - regex: false - })); - - const parsedBadWords = Object.values(badWordsRaw).flat(); - - const result = [ - ...this.checkWords(customAutomodPhrases), - ...this.checkWords((await this.message.guild.hasFeature('excludeDefaultAutomod')) ? [] : parsedBadWords), - ...this.checkWords((await this.message.guild.hasFeature('excludeAutomodScamLinks')) ? [] : badLinks) - ]; - - if (result.length === 0) break traditional; - - const highestOffence = result.sort((a, b) => b.severity - a.severity)[0]; - - if (highestOffence.severity === undefined || highestOffence.severity === null) { - void this.message.guild.sendLogChannel('error', { - embeds: [ - { - title: 'AutoMod Error', - description: `Unable to find severity information for ${format.inlineCode(highestOffence.match)}`, - color: colors.error - } - ] - }); - } else { - const color = this.punish(highestOffence); - void this.log(highestOffence, color, result); - } - } - - other: { - if (this.isImmune) break other; - if (!this.punished && (await this.message.guild.hasFeature('delScamMentions'))) void this.checkScamMentions(); - } - - if (!this.punished && (await this.message.guild.hasFeature('perspectiveApi'))) void this.checkPerspectiveApi(); - } - - /** - * Checks if any of the words provided are in the message - * @param words The words to check for - * @returns The blacklisted words found in the message - */ - private checkWords(words: BadWordDetails[]): BadWordDetails[] { - if (words.length === 0) return []; - - const matchedWords: BadWordDetails[] = []; - for (const word of words) { - if (word.regex) { - if (new RegExp(word.match).test(this.format(word.match, word))) { - matchedWords.push(word); - } - } else { - if (this.format(this.message.content, word).includes(this.format(word.match, word))) { - matchedWords.push(word); - } - } - } - return matchedWords; - } - - /** - * If the message contains '@everyone' or '@here' and it contains a common scam phrase, it will be deleted - * @returns - */ - private async checkScamMentions() { - const includes = (c: string) => this.message.content.toLocaleLowerCase().includes(c); - if (!includes('@everyone') && !includes('@here')) return; - // It would be bad if we deleted a message that actually pinged @everyone or @here - if ( - this.message.member?.permissionsIn(this.message.channelId).has(PermissionFlagsBits.MentionEveryone) || - this.message.mentions.everyone - ) - return; - - if ( - includes('steam') || - includes('www.youtube.com') || - includes('youtu.be') || - includes('nitro') || - includes('1 month') || - includes('3 months') || - includes('personalize your profile') || - includes('even more') || - includes('xbox and discord') || - includes('left over') || - includes('check this lol') || - includes('airdrop') - ) { - const color = this.punish({ severity: Severity.TEMP_MUTE, reason: 'everyone mention and scam phrase' } as BadWordDetails); - void this.message.guild!.sendLogChannel('automod', { - embeds: [ - new EmbedBuilder() - .setTitle(`[Severity ${Severity.TEMP_MUTE}] Mention Scam Deleted`) - .setDescription( - `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})` - ) - .addFields({ - name: 'Message Content', - value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` - }) - .setColor(color) - .setTimestamp() - ], - components: [this.buttons(this.message.author.id, 'everyone mention and scam phrase')] - }); - } - } - - private async checkPerspectiveApi() { - return; - if (!this.message.client.config.isDevelopment) return; - - if (!this.message.content) return; - this.message.client.perspective.comments.analyze( - { - key: this.message.client.config.credentials.perspectiveApiKey, - resource: { - comment: { - text: this.message.content - }, - requestedAttributes: { - TOXICITY: {}, - SEVERE_TOXICITY: {}, - IDENTITY_ATTACK: {}, - INSULT: {}, - PROFANITY: {}, - THREAT: {}, - SEXUALLY_EXPLICIT: {}, - FLIRTATION: {} - } - } - }, - (err: any, response: any) => { - if (err) return console.log(err?.message); - - const normalize = (val: number, min: number, max: number) => (val - min) / (max - min); - - const color = (val: number) => { - if (val >= 0.5) { - const x = 194 - Math.round(normalize(val, 0.5, 1) * 194); - return chalk.rgb(194, x, 0)(val); - } else { - const x = Math.round(normalize(val, 0, 0.5) * 194); - return chalk.rgb(x, 194, 0)(val); - } - }; - - console.log(chalk.cyan(this.message.content)); - Object.entries(response.data.attributeScores) - .sort(([a], [b]) => a.localeCompare(b)) - .forEach(([key, value]: any[]) => console.log(chalk.white(key), color(value.summaryScore.value))); - } - ); - } - - /** - * Format a string according to the word options - * @param string The string to format - * @param wordOptions The word options to format with - * @returns The formatted string - */ - private format(string: string, wordOptions: BadWordDetails) { - const temp = wordOptions.ignoreCapitalization ? string.toLowerCase() : string; - return wordOptions.ignoreSpaces ? temp.replace(/ /g, '') : temp; - } - - /** - * Punishes the user based on the severity of the offense - * @param highestOffence The highest offense to punish the user for - * @returns The color of the embed that the log should, based on the severity of the offense - */ - private punish(highestOffence: BadWordDetails) { - let color; - switch (highestOffence.severity) { - case Severity.DELETE: { - color = colors.lightGray; - void this.message.delete().catch((e) => deleteError.bind(this, e)); - this.punished = true; - break; - } - case Severity.WARN: { - color = colors.yellow; - void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.bushWarn({ - moderator: this.message.guild!.members.me!, - reason: `[AutoMod] ${highestOffence.reason}` - }); - this.punished = true; - break; - } - case Severity.TEMP_MUTE: { - color = colors.orange; - void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.bushMute({ - moderator: this.message.guild!.members.me!, - reason: `[AutoMod] ${highestOffence.reason}`, - duration: 900_000 // 15 minutes - }); - this.punished = true; - break; - } - case Severity.PERM_MUTE: { - color = colors.red; - void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.bushMute({ - moderator: this.message.guild!.members.me!, - reason: `[AutoMod] ${highestOffence.reason}`, - duration: 0 // permanent - }); - this.punished = true; - break; - } - default: { - throw new Error(`Invalid severity: ${highestOffence.severity}`); - } - } - - return color; - - async function deleteError(this: AutoMod, e: Error | any) { - void this.message.guild?.sendLogChannel('error', { - embeds: [ - { - title: 'AutoMod Error', - description: `Unable to delete triggered message.`, - fields: [{ name: 'Error', value: await this.message.client.utils.codeblock(`${formatError(e)}`, 1024, 'js', true) }], - color: colors.error - } - ] - }); - } - } - - /** - * Log an automod infraction to the guild's specified automod log channel - * @param highestOffence The highest severity word found in the message - * @param color The color that the log embed should be (based on the severity) - * @param offenses The other offenses that were also matched in the message - */ - private async log(highestOffence: BadWordDetails, color: number, offenses: BadWordDetails[]) { - void this.message.client.console.info( - 'autoMod', - `Severity <<${highestOffence.severity}>> action performed on <<${this.message.author.tag}>> (<<${ - this.message.author.id - }>>) in <<#${(this.message.channel as TextChannel).name}>> in <<${this.message.guild!.name}>>` - ); - - await this.message.guild!.sendLogChannel('automod', { - embeds: [ - new EmbedBuilder() - .setTitle(`[Severity ${highestOffence.severity}] Automod Action Performed`) - .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:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}` - ) - .addFields({ - name: 'Message Content', - value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` - }) - .setColor(color) - .setTimestamp() - .setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() }) - ], - components: highestOffence.severity >= 2 ? [this.buttons(this.message.author.id, highestOffence.reason)] : undefined - }); - } - - private buttons(userId: Snowflake, reason: string): ActionRowBuilder<ButtonBuilder> { - return new ActionRowBuilder<ButtonBuilder>().addComponents( - new ButtonBuilder({ - style: ButtonStyle.Danger, - label: 'Ban User', - customId: `automod;ban;${userId};${reason}` - }), - new ButtonBuilder({ - style: ButtonStyle.Success, - label: 'Unmute User', - customId: `automod;unmute;${userId}` - }) - ); - } - - /** - * Handles the ban button in the automod log. - * @param interaction The button interaction. - */ - public static async handleInteraction(interaction: ButtonInteraction) { - if (!interaction.memberPermissions?.has(PermissionFlagsBits.BanMembers)) - return interaction.reply({ - content: `${emojis.error} You are missing the **Ban Members** permission.`, - ephemeral: true - }); - const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';') as [ - 'ban' | 'unmute', - string, - string - ]; - - if (!(['ban', 'unmute'] as const).includes(action)) throw new TypeError(`Invalid automod button action: ${action}`); - - const victim = await interaction.guild!.members.fetch(userId).catch(() => null); - const moderator = - interaction.member instanceof GuildMember - ? interaction.member - : await interaction.guild!.members.fetch(interaction.user.id); - - switch (action) { - case 'ban': { - if (!interaction.guild?.members.me?.permissions.has('BanMembers')) - return interaction.reply({ - content: `${emojis.error} I do not have permission to ${action} members.`, - ephemeral: true - }); - - const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; - if (check !== true) return interaction.reply({ content: check, ephemeral: true }); - - const result = await interaction.guild?.bushBan({ - user: userId, - reason, - moderator: interaction.user.id, - evidence: (interaction.message as Message).url ?? undefined - }); - - const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId; - - const content = (() => { - if (result === unmuteResponse.SUCCESS) { - return `${emojis.success} Successfully banned ${format.input(victimUserFormatted)}.`; - } else if (result === unmuteResponse.DM_ERROR) { - return `${emojis.warn} Banned ${format.input(victimUserFormatted)} however I could not send them a dm.`; - } else { - return `${emojis.error} Could not ban ${format.input(victimUserFormatted)}: \`${result}\` .`; - } - })(); - - return interaction.reply({ - content: content, - ephemeral: true - }); - } - - case 'unmute': { - if (!victim) - return interaction.reply({ - content: `${emojis.error} Cannot find member, they may have left the server.`, - ephemeral: true - }); - - if (!interaction.guild) - return interaction.reply({ - content: `${emojis.error} This is weird, I don't seem to be in the server...`, - ephemeral: true - }); - - const check = await Moderation.permissionCheck(moderator, victim, 'unmute', true); - if (check !== true) return interaction.reply({ content: check, ephemeral: true }); - - const check2 = await Moderation.checkMutePermissions(interaction.guild); - if (check2 !== true) - return interaction.reply({ content: UnmuteCommand.formatCode('/', victim!, check2), ephemeral: true }); - - const result = await victim.bushUnmute({ - reason, - moderator: interaction.member as GuildMember, - evidence: (interaction.message as Message).url ?? undefined - }); - - const victimUserFormatted = victim.user.tag; - - const content = (() => { - if (result === unmuteResponse.SUCCESS) { - return `${emojis.success} Successfully unmuted ${format.input(victimUserFormatted)}.`; - } else if (result === unmuteResponse.DM_ERROR) { - return `${emojis.warn} Unmuted ${format.input(victimUserFormatted)} however I could not send them a dm.`; - } else { - return `${emojis.error} Could not unmute ${format.input(victimUserFormatted)}: \`${result}\` .`; - } - })(); - - return interaction.reply({ - content: content, - ephemeral: true - }); - } - } - } -} - -/** - * The severity of the blacklisted word - */ -export const enum Severity { - /** - * Delete message - */ - DELETE, - - /** - * Delete message and warn user - */ - WARN, - - /** - * Delete message and mute user for 15 minutes - */ - TEMP_MUTE, - - /** - * Delete message and mute user permanently - */ - PERM_MUTE -} - -/** - * Details about a blacklisted word - */ -export interface BadWordDetails { - /** - * The word that is blacklisted - */ - match: string; - - /** - * The severity of the word - */ - severity: Severity | 1 | 2 | 3; - - /** - * Whether or not to ignore spaces when checking for the word - */ - ignoreSpaces: boolean; - - /** - * Whether or not to ignore case when checking for the word - */ - ignoreCapitalization: boolean; - - /** - * The reason that this word is blacklisted (used for the punishment reason) - */ - reason: string; - - /** - * Whether or not the word is regex - */ - regex: boolean; -} - -/** - * Blacklisted words mapped to their details - */ -export interface BadWords { - [category: string]: BadWordDetails[]; -} diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts deleted file mode 100644 index 02c78ea..0000000 --- a/src/lib/common/ButtonPaginator.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { DeleteButton, type CommandMessage, type SlashMessage } from '#lib'; -import { CommandUtil } from 'discord-akairo'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - EmbedBuilder, - type APIEmbed, - type Message, - type MessageComponentInteraction -} from 'discord.js'; - -/** - * Sends multiple embeds with controls to switch between them - */ -export class ButtonPaginator { - /** - * The current page of the paginator - */ - protected curPage: number; - - /** - * The paginator message - */ - protected sentMessage: Message | undefined; - - /** - * @param message The message that triggered the command - * @param embeds The embeds to switch between - * @param text The optional text to send with the paginator - * @param {} [deleteOnExit=true] Whether the paginator message gets deleted when the exit button is pressed - * @param startOn The page to start from (**not** the index) - */ - protected constructor( - protected message: CommandMessage | SlashMessage, - protected embeds: EmbedBuilder[] | APIEmbed[], - protected text: string | null, - protected deleteOnExit: boolean, - startOn: number - ) { - this.curPage = startOn - 1; - - // add footers - for (let i = 0; i < embeds.length; i++) { - if (embeds[i] instanceof EmbedBuilder) { - (embeds[i] as EmbedBuilder).setFooter({ text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` }); - } else { - (embeds[i] as APIEmbed).footer = { - text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` - }; - } - } - } - - /** - * The number of pages in the paginator - */ - protected get numPages(): number { - return this.embeds.length; - } - - /** - * Sends the paginator message - */ - protected async send() { - this.sentMessage = await this.message.util.reply({ - content: this.text, - embeds: [this.embeds[this.curPage]], - components: [this.getPaginationRow()] - }); - - const collector = this.sentMessage.createMessageComponentCollector({ - filter: (i) => i.customId.startsWith('paginate_'), - time: 300_000 - }); - collector.on('collect', (i) => void this.collect(i)); - collector.on('end', () => void this.end()); - } - - /** - * Handles interactions with the paginator - * @param interaction The interaction received - */ - protected async collect(interaction: MessageComponentInteraction) { - if (interaction.user.id !== this.message.author.id && !this.message.client.config.owners.includes(interaction.user.id)) - return await interaction?.deferUpdate().catch(() => null); - - switch (interaction.customId) { - case 'paginate_beginning': - this.curPage = 0; - await this.edit(interaction); - break; - case 'paginate_back': - this.curPage--; - await this.edit(interaction); - break; - case 'paginate_stop': - if (this.deleteOnExit) { - await interaction.deferUpdate().catch(() => null); - await this.sentMessage!.delete().catch(() => null); - break; - } else { - await interaction - ?.update({ - content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`, - embeds: [], - components: [] - }) - .catch(() => null); - break; - } - case 'paginate_next': - this.curPage++; - await this.edit(interaction); - break; - case 'paginate_end': - this.curPage = this.embeds.length - 1; - await this.edit(interaction); - break; - } - } - - /** - * Ends the paginator - */ - protected async end() { - if (this.sentMessage && !CommandUtil.deletedMessages.has(this.sentMessage.id)) - await this.sentMessage - .edit({ - content: this.text, - embeds: [this.embeds[this.curPage]], - components: [this.getPaginationRow(true)] - }) - .catch(() => null); - } - - /** - * Edits the paginator message - * @param interaction The interaction received - */ - protected async edit(interaction: MessageComponentInteraction) { - await interaction - ?.update({ - content: this.text, - embeds: [this.embeds[this.curPage]], - components: [this.getPaginationRow()] - }) - .catch(() => null); - } - - /** - * Generates the pagination row based on the class properties - * @param disableAll Whether to disable all buttons - * @returns The generated {@link ActionRow} - */ - protected getPaginationRow(disableAll = false) { - return new ActionRowBuilder<ButtonBuilder>().addComponents( - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate_beginning', - emoji: PaginateEmojis.BEGINNING, - disabled: disableAll || this.curPage === 0 - }), - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate_back', - emoji: PaginateEmojis.BACK, - disabled: disableAll || this.curPage === 0 - }), - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate_stop', - emoji: PaginateEmojis.STOP, - disabled: disableAll - }), - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate_next', - emoji: PaginateEmojis.FORWARD, - disabled: disableAll || this.curPage === this.numPages - 1 - }), - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate_end', - emoji: PaginateEmojis.END, - disabled: disableAll || this.curPage === this.numPages - 1 - }) - ); - } - - /** - * 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: CommandMessage | SlashMessage, - embeds: EmbedBuilder[] | APIEmbed[], - text: string | null = null, - deleteOnExit = true, - startOn = 1 - ) { - // 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(); - } -} - -export const PaginateEmojis = { - BEGINNING: { id: '853667381335162910', name: 'w_paginate_beginning', animated: false } as const, - BACK: { id: '853667410203770881', name: 'w_paginate_back', animated: false } as const, - STOP: { id: '853667471110570034', name: 'w_paginate_stop', animated: false } as const, - FORWARD: { id: '853667492680564747', name: 'w_paginate_next', animated: false } as const, - END: { id: '853667514915225640', name: 'w_paginate_end', animated: false } as const -} as const; diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts deleted file mode 100644 index b87d9ef..0000000 --- a/src/lib/common/ConfirmationPrompt.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type CommandMessage, type SlashMessage } from '#lib'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type MessageComponentInteraction, type MessageOptions } from 'discord.js'; - -/** - * Sends a message with buttons for the user to confirm or cancel the action. - */ -export class ConfirmationPrompt { - /** - * @param message The message that triggered the command - * @param messageOptions Options for sending the message - */ - protected constructor(protected message: CommandMessage | SlashMessage, protected messageOptions: MessageOptions) {} - - /** - * Sends a message with buttons for the user to confirm or cancel the action. - */ - protected async send(): Promise<boolean> { - this.messageOptions.components = [ - new ActionRowBuilder<ButtonBuilder>().addComponents( - new ButtonBuilder({ style: ButtonStyle.Success, customId: 'confirmationPrompt_confirm', label: 'Yes' }), - new ButtonBuilder({ style: ButtonStyle.Danger, customId: 'confirmationPrompt_cancel', label: 'No' }) - ) - ]; - - const msg = await this.message.channel!.send(this.messageOptions); - - return await new Promise<boolean>((resolve) => { - let responded = false; - const collector = msg.createMessageComponentCollector({ - filter: (interaction) => interaction.message?.id == msg.id, - time: 300_000 - }); - - collector.on('collect', async (interaction: MessageComponentInteraction) => { - await interaction.deferUpdate().catch(() => undefined); - if (interaction.user.id == this.message.author.id || this.message.client.config.owners.includes(interaction.user.id)) { - if (interaction.customId === 'confirmationPrompt_confirm') { - responded = true; - collector.stop(); - resolve(true); - } else if (interaction.customId === 'confirmationPrompt_cancel') { - responded = true; - collector.stop(); - resolve(false); - } - } - }); - - collector.on('end', async () => { - await msg.delete().catch(() => undefined); - if (!responded) resolve(false); - }); - }); - } - - /** - * Sends a message with buttons for the user to confirm or cancel the action. - * @param message The message that triggered the command - * @param sendOptions Options for sending the message - */ - public static async send(message: CommandMessage | SlashMessage, sendOptions: MessageOptions): Promise<boolean> { - return new ConfirmationPrompt(message, sendOptions).send(); - } -} diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts deleted file mode 100644 index 340d07f..0000000 --- a/src/lib/common/DeleteButton.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { PaginateEmojis, type CommandMessage, type SlashMessage } from '#lib'; -import { CommandUtil } from 'discord-akairo'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - MessageComponentInteraction, - MessageEditOptions, - MessagePayload, - type MessageOptions -} from 'discord.js'; - -/** - * Sends a message with a button for the user to delete it. - */ -export class DeleteButton { - /** - * @param message The message to respond to - * @param messageOptions The send message options - */ - protected constructor(protected message: CommandMessage | SlashMessage, protected messageOptions: MessageOptions) {} - - /** - * Sends a message with a button for the user to delete it. - */ - protected async send() { - this.updateComponents(); - - const msg = await this.message.util.reply(this.messageOptions); - - 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 || this.message.client.config.owners.includes(interaction.user.id)) { - if (msg.deletable && !CommandUtil.deletedMessages.has(msg.id)) await msg.delete(); - } - }); - - collector.on('end', async () => { - this.updateComponents(true, true); - await msg.edit(<string | MessagePayload | MessageEditOptions>this.messageOptions).catch(() => undefined); - }); - } - - /** - * Generates the components for the message - * @param edit Whether or not the message is being edited - * @param disable Whether or not to disable the buttons - */ - protected updateComponents(edit = false, disable = false): void { - this.messageOptions.components = [ - new ActionRowBuilder<ButtonBuilder>().addComponents( - new ButtonBuilder({ - style: ButtonStyle.Primary, - customId: 'paginate__stop', - emoji: PaginateEmojis.STOP, - disabled: disable - }) - ) - ]; - if (edit) { - this.messageOptions.reply = undefined; - } - } - - /** - * 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 - */ - public static async send(message: CommandMessage | SlashMessage, options: Omit<MessageOptions, 'components'>) { - return new DeleteButton(message, options).send(); - } -} diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts deleted file mode 100644 index 4f891b7..0000000 --- a/src/lib/common/HighlightManager.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { addToArray, format, Highlight, removeFromArray, timestamp, type HighlightWord } from '#lib'; -import assert from 'assert/strict'; -import { - ChannelType, - Collection, - GuildMember, - type Channel, - type Client, - type Message, - type Snowflake, - type TextBasedChannel -} from 'discord.js'; -import { colors, Time } from '../utils/BushConstants.js'; -import { sanitizeInputForDiscord } from './util/Format.js'; - -const NOTIFY_COOLDOWN = 5 * Time.Minute; -const OWNER_NOTIFY_COOLDOWN = 5 * Time.Minute; -const LAST_MESSAGE_COOLDOWN = 5 * Time.Minute; - -type users = Set<Snowflake>; -type channels = Set<Snowflake>; -type word = HighlightWord; -type guild = Snowflake; -type user = Snowflake; -type lastMessage = Date; -type lastDM = Message; - -type lastDmInfo = [lastDM: lastDM, guild: guild, channel: Snowflake, highlights: HighlightWord[]]; - -export class HighlightManager { - /** - * Cached guild highlights. - */ - public readonly guildHighlights = new Collection<guild, Collection<word, users>>(); - - //~ /** - //~ * Cached global highlights. - //~ */ - //~ public readonly globalHighlights = new Collection<word, users>(); - - /** - * A collection of cooldowns of when a user last sent a message in a particular guild. - */ - public readonly userLastTalkedCooldown = new Collection<guild, Collection<user, lastMessage>>(); - - /** - * Users that users have blocked - */ - p |
