aboutsummaryrefslogtreecommitdiff
path: root/src/lib/common
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-08-18 22:42:12 -0400
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-08-18 22:42:12 -0400
commit2356d2c44736fb83021dacb551625852111c8ce6 (patch)
tree10408d22fdd7a358d2f5c5917c3b59e55aa4c19d /src/lib/common
parent8aed6f93f7740c592cbc0e2f9fd3269c05286077 (diff)
downloadtanzanite-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.ts529
-rw-r--r--src/lib/common/ButtonPaginator.ts219
-rw-r--r--src/lib/common/ConfirmationPrompt.ts64
-rw-r--r--src/lib/common/DeleteButton.ts78
-rw-r--r--src/lib/common/HighlightManager.ts485
-rw-r--r--src/lib/common/Sentry.ts24
-rw-r--r--src/lib/common/tags.ts34
-rw-r--r--src/lib/common/typings/BushInspectOptions.ts123
-rw-r--r--src/lib/common/typings/CodeBlockLang.ts311
-rw-r--r--src/lib/common/util/Arg.ts192
-rw-r--r--src/lib/common/util/Format.ts119
-rw-r--r--src/lib/common/util/Minecraft.ts349
-rw-r--r--src/lib/common/util/Minecraft_Test.ts86
-rw-r--r--src/lib/common/util/Moderation.ts556
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