From 2356d2c44736fb83021dacb551625852111c8ce6 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:42:12 -0400 Subject: restructure, experimental presence and member automod, fixed bugs probably made some more bugs --- .../discord-akairo/BushArgumentTypeCaster.ts | 3 - src/lib/extensions/discord-akairo/BushClient.ts | 586 --------- src/lib/extensions/discord-akairo/BushCommand.ts | 586 --------- .../discord-akairo/BushCommandHandler.ts | 37 - src/lib/extensions/discord-akairo/BushInhibitor.ts | 19 - .../discord-akairo/BushInhibitorHandler.ts | 3 - src/lib/extensions/discord-akairo/BushListener.ts | 3 - .../discord-akairo/BushListenerHandler.ts | 3 - src/lib/extensions/discord-akairo/BushTask.ts | 3 - .../extensions/discord-akairo/BushTaskHandler.ts | 3 - src/lib/extensions/discord-akairo/SlashMessage.ts | 3 - src/lib/extensions/discord.js/BushClientEvents.ts | 200 ---- src/lib/extensions/discord.js/ExtendedGuild.ts | 916 -------------- .../extensions/discord.js/ExtendedGuildMember.ts | 1255 -------------------- src/lib/extensions/discord.js/ExtendedMessage.ts | 12 - src/lib/extensions/discord.js/ExtendedUser.ts | 35 - src/lib/extensions/global.ts | 13 - 17 files changed, 3680 deletions(-) delete mode 100644 src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts delete mode 100644 src/lib/extensions/discord-akairo/BushClient.ts delete mode 100644 src/lib/extensions/discord-akairo/BushCommand.ts delete mode 100644 src/lib/extensions/discord-akairo/BushCommandHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushInhibitor.ts delete mode 100644 src/lib/extensions/discord-akairo/BushInhibitorHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushListener.ts delete mode 100644 src/lib/extensions/discord-akairo/BushListenerHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/BushTask.ts delete mode 100644 src/lib/extensions/discord-akairo/BushTaskHandler.ts delete mode 100644 src/lib/extensions/discord-akairo/SlashMessage.ts delete mode 100644 src/lib/extensions/discord.js/BushClientEvents.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedGuild.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedGuildMember.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedMessage.ts delete mode 100644 src/lib/extensions/discord.js/ExtendedUser.ts delete mode 100644 src/lib/extensions/global.ts (limited to 'src/lib/extensions') diff --git a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts b/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts deleted file mode 100644 index def7ad6..0000000 --- a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type CommandMessage } from '#lib'; - -export type BushArgumentTypeCaster = (message: CommandMessage, phrase: string) => R; diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts deleted file mode 100644 index 9ca02a2..0000000 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ /dev/null @@ -1,586 +0,0 @@ -import { - abbreviatedNumber, - contentWithDuration, - discordEmoji, - duration, - durationSeconds, - globalUser, - messageLink, - permission, - roleWithDuration, - snowflake -} from '#args'; -import { BushClientEvents, emojis, formatError, inspect } from '#lib'; -import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; -import * as Sentry from '@sentry/node'; -import { - AkairoClient, - ArgumentTypeCaster, - ContextMenuCommandHandler, - version as akairoVersion, - type ArgumentPromptData, - type OtherwiseContentSupplier -} from 'discord-akairo'; -import { - ActivityType, - GatewayIntentBits, - MessagePayload, - Options, - Partials, - Structures, - version as discordJsVersion, - type Awaitable, - type If, - type InteractionReplyOptions, - type Message, - type MessageEditOptions, - type MessageOptions, - type ReplyMessageOptions, - type Snowflake, - type UserResolvable, - type WebhookEditMessageOptions -} from 'discord.js'; -import type EventEmitter from 'events'; -import { google } from 'googleapis'; -import path from 'path'; -import readline from 'readline'; -import type { Options as SequelizeOptions, Sequelize as SequelizeType } from 'sequelize'; -import { fileURLToPath } from 'url'; -import type { Config } from '../../../../config/Config.js'; -import { tinyColor } from '../../../arguments/tinyColor.js'; -import UpdateCacheTask from '../../../tasks/cache/updateCache.js'; -import UpdateStatsTask from '../../../tasks/feature/updateStats.js'; -import { HighlightManager } from '../../common/HighlightManager.js'; -import { ActivePunishment } from '../../models/instance/ActivePunishment.js'; -import { Guild as GuildDB } from '../../models/instance/Guild.js'; -import { Highlight } from '../../models/instance/Highlight.js'; -import { Level } from '../../models/instance/Level.js'; -import { ModLog } from '../../models/instance/ModLog.js'; -import { Reminder } from '../../models/instance/Reminder.js'; -import { StickyRole } from '../../models/instance/StickyRole.js'; -import { Global } from '../../models/shared/Global.js'; -import { GuildCount } from '../../models/shared/GuildCount.js'; -import { MemberCount } from '../../models/shared/MemberCount.js'; -import { Shared } from '../../models/shared/Shared.js'; -import { Stat } from '../../models/shared/Stat.js'; -import { AllowedMentions } from '../../utils/AllowedMentions.js'; -import { BushCache } from '../../utils/BushCache.js'; -import { BushClientUtils } from '../../utils/BushClientUtils.js'; -import { BushLogger } from '../../utils/BushLogger.js'; -import { ExtendedGuild } from '../discord.js/ExtendedGuild.js'; -import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js'; -import { ExtendedMessage } from '../discord.js/ExtendedMessage.js'; -import { ExtendedUser } from '../discord.js/ExtendedUser.js'; -import { BushCommandHandler } from './BushCommandHandler.js'; -import { BushInhibitorHandler } from './BushInhibitorHandler.js'; -import { BushListenerHandler } from './BushListenerHandler.js'; -import { BushTaskHandler } from './BushTaskHandler.js'; -const { Sequelize } = (await import('sequelize')).default; - -declare module 'discord.js' { - export interface Client extends EventEmitter { - /** The ID of the owner(s). */ - ownerID: Snowflake | Snowflake[]; - /** The ID of the superUser(s). */ - superUserID: Snowflake | Snowflake[]; - /** Whether or not the client is ready. */ - customReady: boolean; - /** The configuration for the client. */ - readonly config: Config; - /** Stats for the client. */ - readonly stats: BushStats; - /** The handler for the bot's listeners. */ - readonly listenerHandler: BushListenerHandler; - /** The handler for the bot's command inhibitors. */ - readonly inhibitorHandler: BushInhibitorHandler; - /** The handler for the bot's commands. */ - readonly commandHandler: BushCommandHandler; - /** The handler for the bot's tasks. */ - readonly taskHandler: BushTaskHandler; - /** The handler for the bot's context menu commands. */ - readonly contextMenuCommandHandler: ContextMenuCommandHandler; - /** The database connection for this instance of the bot (production, beta, or development). */ - readonly instanceDB: SequelizeType; - /** The database connection that is shared between all instances of the bot. */ - readonly sharedDB: SequelizeType; - /** A custom logging system for the bot. */ - readonly logger: BushLogger; - /** Cached global and guild database data. */ - readonly cache: BushCache; - /** Sentry error reporting for the bot. */ - readonly sentry: typeof Sentry; - /** Manages most aspects of the highlight command */ - readonly highlightManager: HighlightManager; - /** The perspective api */ - perspective: any; - /** Client utilities. */ - readonly utils: BushClientUtils; - /** A custom logging system for the bot. */ - get console(): BushLogger; - on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - emit(event: K, ...args: BushClientEvents[K]): boolean; - off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - removeAllListeners(event?: K): this; - /** - * Checks if a user is the owner of this bot. - * @param user - User to check. - */ - isOwner(user: UserResolvable): boolean; - /** - * Checks if a user is a super user of this bot. - * @param user - User to check. - */ - isSuperUser(user: UserResolvable): boolean; - } -} - -export type ReplyMessageType = string | MessagePayload | ReplyMessageOptions; -export type EditMessageType = string | MessageEditOptions | MessagePayload; -export type SlashSendMessageType = string | MessagePayload | InteractionReplyOptions; -export type SlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions; -export type SendMessageType = string | MessagePayload | MessageOptions; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -/** - * The main hub for interacting with the Discord API. - */ -export class BushClient extends AkairoClient { - public declare ownerID: Snowflake[]; - public declare superUserID: Snowflake[]; - - /** - * Whether or not the client is ready. - */ - public override customReady = false; - - /** - * Stats for the client. - */ - public override readonly stats: BushStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n }; - - /** - * The handler for the bot's listeners. - */ - public override readonly listenerHandler: BushListenerHandler; - - /** - * The handler for the bot's command inhibitors. - */ - public override readonly inhibitorHandler: BushInhibitorHandler; - - /** - * The handler for the bot's commands. - */ - public override readonly commandHandler: BushCommandHandler; - - /** - * The handler for the bot's tasks. - */ - public override readonly taskHandler: BushTaskHandler; - - /** - * The handler for the bot's context menu commands. - */ - public override readonly contextMenuCommandHandler: ContextMenuCommandHandler; - - /** - * The database connection for this instance of the bot (production, beta, or development). - */ - public override readonly instanceDB: SequelizeType; - - /** - * The database connection that is shared between all instances of the bot. - */ - public override readonly sharedDB: SequelizeType; - - /** - * A custom logging system for the bot. - */ - public override readonly logger: BushLogger = new BushLogger(this); - - /** - * Cached global and guild database data. - */ - public override readonly cache = new BushCache(); - - /** - * Sentry error reporting for the bot. - */ - public override readonly sentry!: typeof Sentry; - - /** - * Manages most aspects of the highlight command - */ - public override readonly highlightManager: HighlightManager = new HighlightManager(this); - - /** - * The perspective api - */ - public override perspective: any; - - /** - * Client utilities. - */ - public override readonly utils: BushClientUtils = new BushClientUtils(this); - - /** - * @param config The configuration for the client. - */ - public constructor( - /** - * The configuration for the client. - */ - public override readonly config: Config - ) { - super({ - ownerID: config.owners, - intents: Object.keys(GatewayIntentBits) - .map((i) => (typeof i === 'string' ? GatewayIntentBits[i as keyof typeof GatewayIntentBits] : i)) - .reduce((acc, p) => acc | p, 0), - partials: Object.keys(Partials).map((p) => Partials[p as keyof typeof Partials]), - presence: { - activities: [{ name: 'Beep Boop', type: ActivityType.Watching }], - status: 'online' - }, - allowedMentions: AllowedMentions.none(), // no mentions by default - makeCache: Options.cacheWithLimits({}), - failIfNotExists: false, - rest: { api: 'https://canary.discord.com/api' } - }); - patch(this); - - this.token = config.token as If; - - /* =-=-= handlers =-=-= */ - this.listenerHandler = new BushListenerHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'listeners'), - automateCategories: true - }); - this.inhibitorHandler = new BushInhibitorHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'inhibitors'), - automateCategories: true - }); - this.taskHandler = new BushTaskHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'tasks'), - automateCategories: true - }); - - const modify = async ( - message: Message, - text: string | MessagePayload | MessageOptions | OtherwiseContentSupplier, - data: ArgumentPromptData, - replaceError: boolean - ) => { - const ending = '\n\n Type **cancel** to cancel the command'; - const options = typeof text === 'function' ? await text(message, data) : text; - const search = '{error}', - replace = emojis.error; - - if (typeof options === 'string') return (replaceError ? options.replace(search, replace) : options) + ending; - - if (options instanceof MessagePayload) { - if (options.options.content) { - if (replaceError) options.options.content = options.options.content.replace(search, replace); - options.options.content += ending; - } - } else if (options.content) { - if (replaceError) options.content = options.content.replace(search, replace); - options.content += ending; - } - return options; - }; - - this.commandHandler = new BushCommandHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'commands'), - prefix: async ({ guild }: Message) => { - if (this.config.isDevelopment) return 'dev '; - if (!guild) return this.config.prefix; - const prefix = await guild.getSetting('prefix'); - return (prefix ?? this.config.prefix) as string; - }, - allowMention: true, - handleEdits: true, - commandUtil: true, - commandUtilLifetime: 300_000, // 5 minutes - argumentDefaults: { - prompt: { - start: 'Placeholder argument prompt. **If you see this please tell my developers**.', - retry: 'Placeholder failed argument prompt. **If you see this please tell my developers**.', - modifyStart: (message, text, data) => modify(message, text, data, false), - modifyRetry: (message, text, data) => modify(message, text, data, true), - timeout: ':hourglass: You took too long the command has been cancelled.', - ended: 'You exceeded the maximum amount of tries the command has been cancelled', - cancel: 'The command has been cancelled', - retries: 3, - time: 3e4 - }, - otherwise: '' - }, - automateCategories: false, - autoRegisterSlashCommands: true, - skipBuiltInPostInhibitors: true, - aliasReplacement: /-/g - }); - this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, { - directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'), - automateCategories: true - }); - - /* =-=-= databases =-=-= */ - const sharedDBOptions: SequelizeOptions = { - username: this.config.db.username, - password: this.config.db.password, - dialect: 'postgres', - host: this.config.db.host, - port: this.config.db.port, - logging: this.config.logging.db ? (sql) => this.logger.debug(sql) : false, - timezone: 'America/New_York' - }; - this.instanceDB = new Sequelize({ - ...sharedDBOptions, - database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot' - }); - this.sharedDB = new Sequelize({ - ...sharedDBOptions, - database: 'bushbot-shared' - }); - - this.sentry = Sentry; - } - - /** - * A custom logging system for the bot. - */ - public override get console(): BushLogger { - return this.logger; - } - - /** - * Extends discord.js structures before the client is instantiated. - */ - public static extendStructures(): void { - Structures.extend('GuildMember', () => ExtendedGuildMember); - Structures.extend('Guild', () => ExtendedGuild); - Structures.extend('Message', () => ExtendedMessage); - Structures.extend('User', () => ExtendedUser); - } - - /** - * Initializes the bot. - */ - public async init() { - if (parseInt(process.versions.node.split('.')[0]) < 17) { - void (await this.console.error('version', `Please use node <>, not <<${process.version}>>.`, false)); - process.exit(2); - } - - this.setMaxListeners(20); - - this.perspective = await google.discoverAPI('https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1'); - - this.commandHandler.useInhibitorHandler(this.inhibitorHandler); - this.commandHandler.useListenerHandler(this.listenerHandler); - this.commandHandler.useTaskHandler(this.taskHandler); - this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler); - this.commandHandler.ignorePermissions = this.config.owners; - this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.shared.superUsers])]; - const emitters: Emitters = { - client: this, - commandHandler: this.commandHandler, - inhibitorHandler: this.inhibitorHandler, - listenerHandler: this.listenerHandler, - taskHandler: this.taskHandler, - contextMenuCommandHandler: this.contextMenuCommandHandler, - process, - stdin: rl, - gateway: this.ws, - rest: this.rest, - ws: this.ws - }; - this.listenerHandler.setEmitters(emitters); - this.commandHandler.resolver.addTypes({ - duration: duration, - contentWithDuration: contentWithDuration, - permission: permission, - snowflake: snowflake, - discordEmoji: discordEmoji, - roleWithDuration: roleWithDuration, - abbreviatedNumber: abbreviatedNumber, - durationSeconds: durationSeconds, - globalUser: globalUser, - messageLink: messageLink, - tinyColor: tinyColor - }); - - this.sentry.setTag('process', process.pid.toString()); - this.sentry.setTag('discord.js', discordJsVersion); - this.sentry.setTag('discord-akairo', akairoVersion); - void this.logger.success('startup', `Successfully connected to <>.`, false); - - // loads all the handlers - const handlers = { - commands: this.commandHandler, - contextMenuCommands: this.contextMenuCommandHandler, - listeners: this.listenerHandler, - inhibitors: this.inhibitorHandler, - tasks: this.taskHandler - }; - const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) => - handler - .loadAll() - .then(() => { - void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false); - }) - .catch((e) => { - void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${formatError(e)}`, false); - if (process.argv.includes('dry')) process.exit(1); - }) - ); - await Promise.allSettled(handlerPromises); - } - - /** - * Connects to the database, initializes models, and creates tables if they do not exist. - */ - public async dbPreInit() { - try { - await this.instanceDB.authenticate(); - GuildDB.initModel(this.instanceDB, this); - ModLog.initModel(this.instanceDB); - ActivePunishment.initModel(this.instanceDB); - Level.initModel(this.instanceDB); - StickyRole.initModel(this.instanceDB); - Reminder.initModel(this.instanceDB); - Highlight.initModel(this.instanceDB); - await this.instanceDB.sync({ alter: true }); // Sync all tables to fix everything if updated - await this.console.success('startup', `Successfully connected to <>.`, false); - } catch (e) { - await this.console.error( - 'startup', - `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, - false - ); - process.exit(2); - } - try { - await this.sharedDB.authenticate(); - Stat.initModel(this.sharedDB); - Global.initModel(this.sharedDB); - Shared.initModel(this.sharedDB); - MemberCount.initModel(this.sharedDB); - GuildCount.initModel(this.sharedDB); - await this.sharedDB.sync({ - // Sync all tables to fix everything if updated - // if another instance restarts we don't want to overwrite new changes made in development - alter: this.config.isDevelopment - }); - await this.console.success('startup', `Successfully connected to <>.`, false); - } catch (e) { - await this.console.error( - 'startup', - `Failed to connect to <> with error:\n${inspect(e, { colors: true, depth: 1 })}`, - false - ); - process.exit(2); - } - } - - /** - * Starts the bot - */ - public async start() { - this.intercept('ready', async (arg, done) => { - const promises = this.guilds.cache - .filter((g) => g.large) - .map((guild) => { - return guild.members.fetch(); - }); - await Promise.all(promises); - this.customReady = true; - this.taskHandler.startAll(); - return done(null, `intercepted ${arg}`); - }); - - try { - await this.highlightManager.syncCache(); - await UpdateCacheTask.init(this); - void this.console.success('startup', `Successfully created <>.`, false); - const stats = await UpdateStatsTask.init(this); - this.stats.commandsUsed = stats.commandsUsed; - this.stats.slashCommandsUsed = stats.slashCommandsUsed; - await this.login(this.token!); - } catch (e) { - await this.console.error('start', inspect(e, { colors: true, depth: 1 }), false); - process.exit(1); - } - } - - /** - * Logs out, terminates the connection to Discord, and destroys the client. - */ - public override destroy(relogin = false): void | Promise { - super.destroy(); - if (relogin) { - return this.login(this.token!); - } - } - - public override isOwner(user: UserResolvable): boolean { - return this.config.owners.includes(this.users.resolveId(user!)!); - } - - public override isSuperUser(user: UserResolvable): boolean { - const userID = this.users.resolveId(user)!; - return this.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID); - } -} - -export interface BushClient extends EventEmitter, PatchedElements, AkairoClient { - on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - emit(event: K, ...args: BushClientEvents[K]): boolean; - off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - removeAllListeners(event?: K): this; -} - -/** - * Various statistics - */ -export interface BushStats { - /** - * The average cpu usage of the bot from the past 60 seconds. - */ - cpu: number | undefined; - - /** - * The total number of times any command has been used. - */ - commandsUsed: bigint; - - /** - * The total number of times any slash command has been used. - */ - slashCommandsUsed: bigint; -} - -export interface Emitters { - client: BushClient; - commandHandler: BushClient['commandHandler']; - inhibitorHandler: BushClient['inhibitorHandler']; - listenerHandler: BushClient['listenerHandler']; - taskHandler: BushClient['taskHandler']; - contextMenuCommandHandler: BushClient['contextMenuCommandHandler']; - process: NodeJS.Process; - stdin: readline.Interface; - gateway: BushClient['ws']; - rest: BushClient['rest']; - ws: BushClient['ws']; -} diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts deleted file mode 100644 index dc2295f..0000000 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ /dev/null @@ -1,586 +0,0 @@ -import { type DiscordEmojiInfo, type RoleWithDuration } from '#args'; -import { - type BushArgumentTypeCaster, - type BushClient, - type BushCommandHandler, - type BushInhibitor, - type BushListener, - type BushTask, - type ParsedDuration -} from '#lib'; -import { - ArgumentMatch, - Command, - CommandUtil, - type AkairoApplicationCommandAutocompleteOption, - type AkairoApplicationCommandChannelOptionData, - type AkairoApplicationCommandChoicesData, - type AkairoApplicationCommandNonOptionsData, - type AkairoApplicationCommandNumericOptionData, - type AkairoApplicationCommandOptionData, - type AkairoApplicationCommandSubCommandData, - type AkairoApplicationCommandSubGroupData, - type ArgumentOptions, - type ArgumentType, - type ArgumentTypeCaster, - type BaseArgumentType, - type CommandOptions, - type ContextMenuCommand, - type MissingPermissionSupplier, - type SlashOption, - type SlashResolveType -} from 'discord-akairo'; -import { - Message, - User, - type ApplicationCommandOptionChoiceData, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - type ApplicationCommandOptionType, - type PermissionResolvable, - type PermissionsString, - type Snowflake -} from 'discord.js'; -import _ from 'lodash'; -import { SlashMessage } from './SlashMessage.js'; - -export interface OverriddenBaseArgumentType extends BaseArgumentType { - commandAlias: BushCommand | null; - command: BushCommand | null; - inhibitor: BushInhibitor | null; - listener: BushListener | null; - task: BushTask | null; - contextMenuCommand: ContextMenuCommand | null; -} - -export interface BaseBushArgumentType extends OverriddenBaseArgumentType { - duration: number | null; - contentWithDuration: ParsedDuration; - permission: PermissionsString | null; - snowflake: Snowflake | null; - discordEmoji: DiscordEmojiInfo | null; - roleWithDuration: RoleWithDuration | null; - abbreviatedNumber: number | null; - globalUser: User | null; - messageLink: Message | null; - durationSeconds: number | null; - tinyColor: string | null; -} - -export type BushArgumentType = keyof BaseBushArgumentType | RegExp; - -interface BaseBushArgumentOptions extends Omit, ExtraArgumentOptions { - id: string; - description: string; - - /** - * The message sent for the prompt and the slash command description. - */ - prompt?: string; - - /** - * The message set for the retry prompt. - */ - retry?: string; - - /** - * Whether or not the argument is optional. - */ - optional?: boolean; - - /** - * The type used for slash commands. Set to false to disable this argument for slash commands. - */ - slashType: AkairoApplicationCommandOptionData['type'] | false; - - /** - * Allows you to get a discord resolved object - * - * ex. get the resolved member object when the type is {@link ApplicationCommandOptionType.User User} - */ - slashResolve?: SlashResolveType; - - /** - * The choices of the option for the user to pick from - */ - choices?: ApplicationCommandOptionChoiceData[]; - - /** - * Whether the option is an autocomplete option - */ - autocomplete?: boolean; - - /** - * When the option type is channel, the allowed types of channels that can be selected - */ - channelTypes?: AkairoApplicationCommandChannelOptionData['channelTypes']; - - /** - * The minimum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option - */ - minValue?: number; - - /** - * The maximum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option - */ - maxValue?: number; -} - -interface ExtraArgumentOptions { - /** - * Restrict this argument to only slash or only text commands. - */ - only?: 'slash' | 'text'; - - /** - * Readable type for the help command. - */ - readableType?: string; - - /** - * Whether the argument is only accessible to the owners. - * @default false - */ - ownerOnly?: boolean; - - /** - * Whether the argument is only accessible to the super users. - * @default false - */ - superUserOnly?: boolean; -} - -export interface BushArgumentOptions extends BaseBushArgumentOptions { - /** - * The type that the argument should be cast to. - * - `string` does not cast to any type. - * - `lowercase` makes the input lowercase. - * - `uppercase` makes the input uppercase. - * - `charCodes` transforms the input to an array of char codes. - * - `number` casts to a number. - * - `integer` casts to an integer. - * - `bigint` casts to a big integer. - * - `url` casts to an `URL` object. - * - `date` casts to a `Date` object. - * - `color` casts a hex code to an integer. - * - `commandAlias` tries to resolve to a command from an alias. - * - `command` matches the ID of a command. - * - `inhibitor` matches the ID of an inhibitor. - * - `listener` matches the ID of a listener. - * - * Possible Discord-related types. - * These types can be plural (add an 's' to the end) and a collection of matching objects will be used. - * - `user` tries to resolve to a user. - * - `member` tries to resolve to a member. - * - `relevant` tries to resolve to a relevant user, works in both guilds and DMs. - * - `channel` tries to resolve to a channel. - * - `textChannel` tries to resolve to a text channel. - * - `voiceChannel` tries to resolve to a voice channel. - * - `stageChannel` tries to resolve to a stage channel. - * - `threadChannel` tries to resolve a thread channel. - * - `role` tries to resolve to a role. - * - `emoji` tries to resolve to a custom emoji. - * - `guild` tries to resolve to a guild. - * - `permission` tries to resolve to a permissions. - * - * Other Discord-related types: - * - `message` tries to fetch a message from an ID within the channel. - * - `guildMessage` tries to fetch a message from an ID within the guild. - * - `relevantMessage` is a combination of the above, works in both guilds and DMs. - * - `invite` tries to fetch an invite object from a link. - * - `userMention` matches a mention of a user. - * - `memberMention` matches a mention of a guild member. - * - `channelMention` matches a mention of a channel. - * - `roleMention` matches a mention of a role. - * - `emojiMention` matches a mention of an emoji. - * - * Misc: - * - `duration` tries to parse duration in milliseconds - * - `contentWithDuration` tries to parse duration in milliseconds and returns the remaining content with the duration - * removed - */ - type?: BushArgumentType | (keyof BaseBushArgumentType)[] | BushArgumentTypeCaster; -} - -export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { - /** - * An array of strings can be used to restrict input to only those strings, case insensitive. - * The array can also contain an inner array of strings, for aliases. - * If so, the first entry of the array will be used as the final argument. - * - * A regular expression can also be used. - * The evaluated argument will be an object containing the `match` and `matches` if global. - */ - customType?: (string | string[])[] | RegExp | string | null; -} - -export type BushMissingPermissionSupplier = (message: CommandMessage | SlashMessage) => Promise | any; - -interface ExtendedCommandOptions { - /** - * Whether the command is hidden from the help command. - */ - hidden?: boolean; - - /** - * The channels the command is limited to run in. - */ - restrictedChannels?: Snowflake[]; - - /** - * The guilds the command is limited to run in. - */ - restrictedGuilds?: Snowflake[]; - - /** - * Show how to use the command. - */ - usage: string[]; - - /** - * Examples for how to use the command. - */ - examples: string[]; - - /** - * A fake command, completely hidden from the help command. - */ - pseudo?: boolean; - - /** - * Allow this command to be run in channels that are blacklisted. - */ - bypassChannelBlacklist?: boolean; - - /** - * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions - */ - helpArgs?: ArgsInfo[]; - - /** - * Extra information about the command, displayed in the help command. - */ - note?: string; -} - -export interface BaseBushCommandOptions - extends Omit, - ExtendedCommandOptions { - /** - * The description of the command. - */ - description: string; - - /** - * The arguments for the command. - */ - args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; - - category: string; - - /** - * Permissions required by the client to run this command. - */ - clientPermissions: bigint | bigint[] | BushMissingPermissionSupplier; - - /** - * Permissions required by the user to run this command. - */ - userPermissions: bigint | bigint[] | BushMissingPermissionSupplier; - - /** - * Whether the argument is only accessible to the owners. - */ - ownerOnly?: boolean; - - /** - * Whether the argument is only accessible to the super users. - */ - superUserOnly?: boolean; -} - -export type BushCommandOptions = Omit | Omit; - -export interface ArgsInfo { - /** - * The name of the argument. - */ - name: string; - - /** - * The description of the argument. - */ - description: string; - - /** - * Whether the argument is optional. - * @default false - */ - optional?: boolean; - - /** - * Whether or not the argument has autocomplete enabled. - * @default false - */ - autocomplete?: boolean; - - /** - * Whether the argument is restricted a certain command. - * @default 'slash & text' - */ - only?: 'slash & text' | 'slash' | 'text'; - - /** - * The method that arguments are matched for text commands. - * @default 'phrase' - */ - match?: ArgumentMatch; - - /** - * The readable type of the argument. - */ - type: string; - - /** - * If {@link match} is 'flag' or 'option', these are the flags that are matched - * @default [] - */ - flag?: string[]; - - /** - * Whether the argument is only accessible to the owners. - * @default false - */ - ownerOnly?: boolean; - - /** - * Whether the argument is only accessible to the super users. - * @default false - */ - superUserOnly?: boolean; -} - -export abstract class BushCommand extends Command { - public declare client: BushClient; - public declare handler: BushCommandHandler; - public declare description: string; - - /** - * Show how to use the command. - */ - public usage: string[]; - - /** - * Examples for how to use the command. - */ - public examples: string[]; - - /** - * The options sent to the constructor - */ - public options: BushCommandOptions; - - /** - * The options sent to the super call - */ - public parsedOptions: CommandOptions; - - /** - * The channels the command is limited to run in. - */ - public restrictedChannels: Snowflake[] | undefined; - - /** - * The guilds the command is limited to run in. - */ - public restrictedGuilds: Snowflake[] | undefined; - - /** - * Whether the command is hidden from the help command. - */ - public hidden: boolean; - - /** - * A fake command, completely hidden from the help command. - */ - public pseudo: boolean; - - /** - * Allow this command to be run in channels that are blacklisted. - */ - public bypassChannelBlacklist: boolean; - - /** - * Info about the arguments for the help command. - */ - public argsInfo?: ArgsInfo[]; - - /** - * Extra information about the command, displayed in the help command. - */ - public note?: string; - - public constructor(id: string, options: BushCommandOptions) { - const options_ = options as BaseBushCommandOptions; - - if (options_.args && typeof options_.args !== 'function') { - options_.args.forEach((_, index: number) => { - if ('customType' in (options_.args?.[index] ?? {})) { - if (!options_.args![index]['type']) options_.args![index]['type'] = options_.args![index]['customType']! as any; - delete options_.args![index]['customType']; - } - }); - } - - const newOptions: Partial = {}; - for (const _key in options_) { - const key = _key as keyof typeof options_; // you got to love typescript - if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') { - const newTextArgs: (ArgumentOptions & ExtraArgumentOptions)[] = []; - const newSlashArgs: SlashOption[] = []; - for (const arg of options_.args) { - if (arg.only !== 'slash' && !options_.slashOnly) { - const newArg: ArgumentOptions & ExtraArgumentOptions = {}; - if ('default' in arg) newArg.default = arg.default; - if ('description' in arg) newArg.description = arg.description; - if ('flag' in arg) newArg.flag = arg.flag; - if ('id' in arg) newArg.id = arg.id; - if ('index' in arg) newArg.index = arg.index; - if ('limit' in arg) newArg.limit = arg.limit; - if ('match' in arg) newArg.match = arg.match; - if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; - if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; - if ('otherwise' in arg) newArg.otherwise = arg.otherwise; - if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { - newArg.prompt = {}; - if ('prompt' in arg) newArg.prompt.start = arg.prompt; - if ('retry' in arg) newArg.prompt.retry = arg.retry; - if ('optional' in arg) newArg.prompt.optional = arg.optional; - } - if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; - if ('unordered' in arg) newArg.unordered = arg.unordered; - if ('ownerOnly' in arg) newArg.ownerOnly = arg.ownerOnly; - if ('superUserOnly' in arg) newArg.superUserOnly = arg.superUserOnly; - newTextArgs.push(newArg); - } - if ( - arg.only !== 'text' && - !('slashOptions' in options_) && - (options_.slash || options_.slashOnly) && - arg.slashType !== false - ) { - const newArg: { - [key in SlashOptionKeys]?: any; - } = { - name: arg.id, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - description: arg.prompt || arg.description || 'No description provided.', - type: arg.slashType - }; - if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; - if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; - if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; - if ('choices' in arg) newArg.choices = arg.choices; - if ('minValue' in arg) newArg.minValue = arg.minValue; - if ('maxValue' in arg) newArg.maxValue = arg.maxValue; - newArg.required = 'optional' in arg ? !arg.optional : true; - newSlashArgs.push(newArg as SlashOption); - } - } - if (newTextArgs.length > 0) newOptions.args = newTextArgs; - if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; - } else if (key === 'clientPermissions' || key === 'userPermissions') { - newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; - } else { - newOptions[key] = options_[key]; - } - } - - super(id, newOptions); - - if (options_.args ?? options_.helpArgs) { - const argsInfo: ArgsInfo[] = []; - const combined = (options_.args ?? options_.helpArgs)!.map((arg) => { - const norm = options_.args - ? options_.args.find((_arg) => _arg.id === ('id' in arg ? arg.id : arg.name)) ?? ({} as BushArgumentOptions) - : ({} as BushArgumentOptions); - const help = options_.helpArgs - ? options_.helpArgs.find((_arg) => _arg.name === ('id' in arg ? arg.id : arg.name)) ?? ({} as ArgsInfo) - : ({} as ArgsInfo); - return { ...norm, ...help }; - }); - - for (const arg of combined) { - const name = _.camelCase('id' in arg ? arg.id : arg.name), - description = arg.description || '*No description provided.*', - optional = arg.optional ?? false, - autocomplete = arg.autocomplete ?? false, - only = arg.only ?? 'slash & text', - match = arg.match ?? 'phrase', - type = match === 'flag' ? 'flag' : arg.readableType ?? arg.type ?? 'string', - flag = arg.flag ? (Array.isArray(arg.flag) ? arg.flag : [arg.flag]) : [], - ownerOnly = arg.ownerOnly ?? false, - superUserOnly = arg.superUserOnly ?? false; - - argsInfo.push({ name, description, optional, autocomplete, only, match, type, flag, ownerOnly, superUserOnly }); - } - - this.argsInfo = argsInfo; - } - - this.description = options_.description; - this.usage = options_.usage; - this.examples = options_.examples; - this.options = options_; - this.parsedOptions = newOptions; - this.hidden = !!options_.hidden; - this.restrictedChannels = options_.restrictedChannels; - this.restrictedGuilds = options_.restrictedGuilds; - this.pseudo = !!options_.pseudo; - this.bypassChannelBlacklist = !!options_.bypassChannelBlacklist; - this.note = options_.note; - } - - /** - * Executes the command. - * @param message - Message that triggered the command. - * @param args - Evaluated arguments. - */ - public abstract override exec(message: CommandMessage, args: any): any; - /** - * Executes the command. - * @param message - Message that triggered the command. - * @param args - Evaluated arguments. - */ - public abstract override exec(message: CommandMessage | SlashMessage, args: any): any; -} - -type SlashOptionKeys = - | keyof AkairoApplicationCommandSubGroupData - | keyof AkairoApplicationCommandNonOptionsData - | keyof AkairoApplicationCommandChannelOptionData - | keyof AkairoApplicationCommandChoicesData - | keyof AkairoApplicationCommandAutocompleteOption - | keyof AkairoApplicationCommandNumericOptionData - | keyof AkairoApplicationCommandSubCommandData; - -interface PseudoArguments extends BaseBushArgumentType { - boolean: boolean; - flag: boolean; - regex: { match: RegExpMatchArray; matches: RegExpExecArray[] }; -} - -export type ArgType = NonNullable; -export type OptArgType = PseudoArguments[T]; - -/** - * `util` is always defined for messages after `'all'` inhibitors - */ -export type CommandMessage = Message & { - /** - * Extra properties applied to the Discord.js message object. - * Utilities for command responding. - * Available on all messages after 'all' inhibitors and built-in inhibitors (bot, client). - * Not all properties of the util are available, depending on the input. - * */ - util: CommandUtil; -}; diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts deleted file mode 100644 index da49af9..0000000 --- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; -import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo'; -import { type Collection, type Message, type PermissionsString } from 'discord.js'; - -export type BushCommandHandlerOptions = CommandHandlerOptions; - -export interface BushCommandHandlerEvents extends CommandHandlerEvents { - commandBlocked: [message: CommandMessage, command: BushCommand, reason: string]; - commandBreakout: [message: CommandMessage, command: BushCommand, /* no util */ breakMessage: Message]; - commandCancelled: [message: CommandMessage, command: BushCommand, /* no util */ retryMessage?: Message]; - commandFinished: [message: CommandMessage, command: BushCommand, args: any, returnValue: any]; - commandInvalid: [message: CommandMessage, command: BushCommand]; - commandLocked: [message: CommandMessage, command: BushCommand]; - commandStarted: [message: CommandMessage, command: BushCommand, args: any]; - cooldown: [message: CommandMessage | SlashMessage, command: BushCommand, remaining: number]; - error: [error: Error, message: /* no util */ Message, command?: BushCommand]; - inPrompt: [message: /* no util */ Message]; - load: [command: BushCommand, isReload: boolean]; - messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string]; - messageInvalid: [message: CommandMessage]; - missingPermissions: [message: CommandMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]]; - remove: [command: BushCommand]; - slashBlocked: [message: SlashMessage, command: BushCommand, reason: string]; - slashError: [error: Error, message: SlashMessage, command: BushCommand]; - slashFinished: [message: SlashMessage, command: BushCommand, args: any, returnValue: any]; - slashMissingPermissions: [message: SlashMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]]; - slashStarted: [message: SlashMessage, command: BushCommand, args: any]; -} - -export class BushCommandHandler extends CommandHandler { - public declare modules: Collection; - public declare categories: Collection>; -} - -export interface BushCommandHandler extends CommandHandler { - findCommand(name: string): BushCommand; -} diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts deleted file mode 100644 index be396cf..0000000 --- a/src/lib/extensions/discord-akairo/BushInhibitor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; -import { Inhibitor } from 'discord-akairo'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Message } from 'discord.js'; - -export abstract class BushInhibitor extends Inhibitor { - /** - * Checks if message should be blocked. - * A return value of true will block the message. - * If returning a Promise, a resolved value of true will block the message. - * - * **Note:** `'all'` type inhibitors do not have {@link Message.util} defined. - * - * @param message - Message being handled. - * @param command - Command to check. - */ - public abstract override exec(message: CommandMessage, command: BushCommand): any; - public abstract override exec(message: CommandMessage | SlashMessage, command: BushCommand): any; -} diff --git a/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts b/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts deleted file mode 100644 index 5e4fb6c..0000000 --- a/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InhibitorHandler } from 'discord-akairo'; - -export class BushInhibitorHandler extends InhibitorHandler {} diff --git a/src/lib/extensions/discord-akairo/BushListener.ts b/src/lib/extensions/discord-akairo/BushListener.ts deleted file mode 100644 index 6917641..0000000 --- a/src/lib/extensions/discord-akairo/BushListener.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Listener } from 'discord-akairo'; - -export abstract class BushListener extends Listener {} diff --git a/src/lib/extensions/discord-akairo/BushListenerHandler.ts b/src/lib/extensions/discord-akairo/BushListenerHandler.ts deleted file mode 100644 index 9c3e4af..0000000 --- a/src/lib/extensions/discord-akairo/BushListenerHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ListenerHandler } from 'discord-akairo'; - -export class BushListenerHandler extends ListenerHandler {} diff --git a/src/lib/extensions/discord-akairo/BushTask.ts b/src/lib/extensions/discord-akairo/BushTask.ts deleted file mode 100644 index 1b70c88..0000000 --- a/src/lib/extensions/discord-akairo/BushTask.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Task } from 'discord-akairo'; - -export abstract class BushTask extends Task {} diff --git a/src/lib/extensions/discord-akairo/BushTaskHandler.ts b/src/lib/extensions/discord-akairo/BushTaskHandler.ts deleted file mode 100644 index 6535abb..0000000 --- a/src/lib/extensions/discord-akairo/BushTaskHandler.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TaskHandler } from 'discord-akairo'; - -export class BushTaskHandler extends TaskHandler {} diff --git a/src/lib/extensions/discord-akairo/SlashMessage.ts b/src/lib/extensions/discord-akairo/SlashMessage.ts deleted file mode 100644 index 0a6669b..0000000 --- a/src/lib/extensions/discord-akairo/SlashMessage.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AkairoMessage } from 'discord-akairo'; - -export class SlashMessage extends AkairoMessage {} diff --git a/src/lib/extensions/discord.js/BushClientEvents.ts b/src/lib/extensions/discord.js/BushClientEvents.ts deleted file mode 100644 index 22bae65..0000000 --- a/src/lib/extensions/discord.js/BushClientEvents.ts +++ /dev/null @@ -1,200 +0,0 @@ -import type { - BanResponse, - CommandMessage, - Guild as GuildDB, - GuildSettings -} from '#lib'; -import type { AkairoClientEvents } from 'discord-akairo'; -import type { - ButtonInteraction, - Collection, - Guild, - GuildMember, - GuildTextBasedChannel, - Message, - ModalSubmitInteraction, - Role, - SelectMenuInteraction, - Snowflake, - User -} from 'discord.js'; - -export interface BushClientEvents extends AkairoClientEvents { - bushBan: [ - victim: GuildMember | User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess?: boolean, - evidence?: string - ]; - bushBlock: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - channel: GuildTextBasedChannel, - evidence?: string - ]; - bushKick: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushMute: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - evidence?: string - ]; - bushPunishRole: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - role: Role, - evidence?: string - ]; - bushPunishRoleRemove: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - role: Role, - evidence?: string - ]; - bushPurge: [ - moderator: User, - guild: Guild, - channel: GuildTextBasedChannel, - messages: Collection - ]; - bushRemoveTimeout: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushTimeout: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - duration: number, - dmSuccess: boolean, - evidence?: string - ]; - bushUnban: [ - victim: User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushUnblock: [ - victim: GuildMember | User, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - channel: GuildTextBasedChannel, - evidence?: string - ]; - bushUnmute: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushUpdateModlog: [ - moderator: GuildMember, - modlogID: string, - key: 'evidence' | 'hidden', - oldModlog: string | boolean, - newModlog: string | boolean - ]; - bushUpdateSettings: [ - setting: Setting, - guild: Guild, - oldValue: GuildDB[Setting], - newValue: GuildDB[Setting], - moderator?: GuildMember - ]; - bushWarn: [ - victim: GuildMember, - moderator: User, - guild: Guild, - reason: string | undefined, - caseID: string, - dmSuccess: boolean, - evidence?: string - ]; - bushLevelUpdate: [ - member: GuildMember, - oldLevel: number, - newLevel: number, - currentXp: number, - message: CommandMessage - ]; - bushLockdown: [ - moderator: GuildMember, - reason: string | undefined, - channelsSuccessMap: Collection, - all?: boolean - ]; - bushUnlockdown: [ - moderator: GuildMember, - reason: string | undefined, - channelsSuccessMap: Collection, - all?: boolean - ]; - massBan: [ - moderator: GuildMember, - guild: Guild, - reason: string | undefined, - results: Collection - ]; - massEvidence: [ - moderator: GuildMember, - guild: Guild, - evidence: string, - lines: string[] - ]; - /* components */ - button: [button: ButtonInteraction]; - selectMenu: [selectMenu: SelectMenuInteraction]; - modal: [modal: ModalSubmitInteraction]; -} - -type Setting = - | GuildSettings - | 'enabledFeatures' - | 'blacklistedChannels' - | 'blacklistedUsers' - | 'disabledCommands'; diff --git a/src/lib/extensions/discord.js/ExtendedGuild.ts b/src/lib/extensions/discord.js/ExtendedGuild.ts deleted file mode 100644 index 3dce7ca..0000000 --- a/src/lib/extensions/discord.js/ExtendedGuild.ts +++ /dev/null @@ -1,916 +0,0 @@ -import { - AllowedMentions, - banResponse, - colors, - dmResponse, - emojis, - permissionsResponse, - punishmentEntryRemove, - type BanResponse, - type GuildFeatures, - type GuildLogType, - type GuildModel -} from '#lib'; -import assert from 'assert/strict'; -import { - AttachmentBuilder, - AttachmentPayload, - Collection, - Guild, - JSONEncodable, - Message, - MessageType, - PermissionFlagsBits, - SnowflakeUtil, - ThreadChannel, - type APIMessage, - type GuildMember, - type GuildMemberResolvable, - type GuildTextBasedChannel, - type MessageOptions, - type MessagePayload, - type NewsChannel, - type Snowflake, - type TextChannel, - type User, - type UserResolvable, - type VoiceChannel, - type Webhook, - type WebhookMessageOptions -} from 'discord.js'; -import _ from 'lodash'; -import * as Moderation from '../../common/util/Moderation.js'; -import { Guild as GuildDB } from '../../models/instance/Guild.js'; -import { ModLogType } from '../../models/instance/ModLog.js'; -import { addOrRemoveFromArray } from '../../utils/BushUtils.js'; - -declare module 'discord.js' { - export interface Guild { - /** - * Checks if the guild has a certain custom feature. - * @param feature The feature to check for - */ - hasFeature(feature: GuildFeatures): Promise; - /** - * Adds a custom feature to the guild. - * @param feature The feature to add - * @param moderator The moderator responsible for adding a feature - */ - addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; - /** - * Removes a custom feature from the guild. - * @param feature The feature to remove - * @param moderator The moderator responsible for removing a feature - */ - removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; - /** - * Makes a custom feature the opposite of what it was before - * @param feature The feature to toggle - * @param moderator The moderator responsible for toggling a feature - */ - toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; - /** - * Fetches a custom setting for the guild - * @param setting The setting to get - */ - getSetting(setting: K): Promise; - /** - * Sets a custom setting for the guild - * @param setting The setting to change - * @param value The value to change the setting to - * @param moderator The moderator to responsible for changing the setting - */ - setSetting>( - setting: K, - value: GuildModel[K], - moderator?: GuildMember - ): Promise; - /** - * Get a the log channel configured for a certain log type. - * @param logType The type of log channel to get. - * @returns Either the log channel or undefined if not configured. - */ - getLogChannel(logType: GuildLogType): Promise; - /** - * Sends a message to the guild's specified logging channel - * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link BushTextChannel.send} - */ - sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise; - /** - * Sends a formatted error message in a guild's error log channel - * @param title The title of the error embed - * @param message The description of the error embed - */ - error(title: string, message: string): Promise; - /** - * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. - * @param options Options for banning the user. - * @returns A string status message of the ban. - */ - bushBan(options: GuildBushBanOptions): Promise; - /** - * {@link bushBan} with less resolving and checks - * @param options Options for banning the user. - * @returns A string status message of the ban. - * **Preconditions:** - * - {@link me} has the `BanMembers` permission - * **Warning:** - * - Doesn't emit bushBan Event - */ - massBanOne(options: GuildMassBanOneOptions): Promise; - /** - * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry. - * @param options Options for unbanning the user. - * @returns A status message of the unban. - */ - bushUnban(options: GuildBushUnbanOptions): Promise; - /** - * Denies send permissions in specified channels - * @param options The options for locking down the guild - */ - lockdown(options: LockdownOptions): Promise; - quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise; - } -} - -/** - * Represents a guild (or a server) on Discord. - * It's recommended to see if a guild is available before performing operations or reading data from it. You can - * check this with {@link ExtendedGuild.available}. - */ -export class ExtendedGuild extends Guild { - /** - * Checks if the guild has a certain custom feature. - * @param feature The feature to check for - */ - public override async hasFeature(feature: GuildFeatures): Promise { - const features = await this.getSetting('enabledFeatures'); - return features.includes(feature); - } - - /** - * Adds a custom feature to the guild. - * @param feature The feature to add - * @param moderator The moderator responsible for adding a feature - */ - public override async addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { - const features = await this.getSetting('enabledFeatures'); - const newFeatures = addOrRemoveFromArray('add', features, feature); - return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; - } - - /** - * Removes a custom feature from the guild. - * @param feature The feature to remove - * @param moderator The moderator responsible for removing a feature - */ - public override async removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { - const features = await this.getSetting('enabledFeatures'); - const newFeatures = addOrRemoveFromArray('remove', features, feature); - return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; - } - - /** - * Makes a custom feature the opposite of what it was before - * @param feature The feature to toggle - * @param moderator The moderator responsible for toggling a feature - */ - public override async toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { - return (await this.hasFeature(feature)) - ? await this.removeFeature(feature, moderator) - : await this.addFeature(feature, moderator); - } - - /** - * Fetches a custom setting for the guild - * @param setting The setting to get - */ - public override async getSetting(setting: K): Promise { - return ( - this.client.cache.guilds.get(this.id)?.[setting] ?? - ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting] - ); - } - - /** - * Sets a custom setting for the guild - * @param setting The setting to change - * @param value The value to change the setting to - * @param moderator The moderator to responsible for changing the setting - */ - public override async setSetting>( - setting: K, - value: GuildDB[K], - moderator?: GuildMember - ): Promise { - const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }); - const oldValue = row[setting] as GuildDB[K]; - row[setting] = value; - this.client.cache.guilds.set(this.id, row.toJSON() as GuildDB); - this.client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator); - return await row.save(); - } - - /** - * Get a the log channel configured for a certain log type. - * @param logType The type of log channel to get. - * @returns Either the log channel or undefined if not configured. - */ - public override async getLogChannel(logType: GuildLogType): Promise { - const channelId = (await this.getSetting('logChannels'))[logType]; - if (!channelId) return undefined; - return ( - (this.channels.cache.get(channelId) as TextChannel | undefined) ?? - ((await this.channels.fetch(channelId)) as TextChannel | null) ?? - undefined - ); - } - - /** - * Sends a message to the guild's specified logging channel - * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link BushTextChannel.send} - */ - public override async sendLogChannel( - logType: GuildLogType, - message: string | MessagePayload | MessageOptions - ): Promise { - const logChannel = await this.getLogChannel(logType); - if (!logChannel || !logChannel.isTextBased()) return; - if ( - !logChannel - .permissionsFor(this.members.me!.id) - ?.has([PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.EmbedLinks]) - ) - return; - - return await logChannel.send(message).catch(() => null); - } - - /** - * Sends a formatted error message in a guild's error log channel - * @param title The title of the error embed - * @param message The description of the error embed - */ - public override async error(title: string, message: string): Promise { - void this.client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); - void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: colors.error }] }); - } - - /** - * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. - * @param options Options for banning the user. - * @returns A string status message of the ban. - */ - public override async bushBan(options: GuildBushBanOptions): Promise { - // checks - if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; - - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; - const user = await this.client.utils.resolveNonCachedUser(options.user); - const moderator = this.client.users.resolve(options.moderator ?? this.client.user!); - if (!user || !moderator) return banResponse.CANNOT_RESOLVE_USER; - - if ((await this.bans.fetch()).has(user.id)) return banResponse.ALREADY_BANNED; - - const ret = await (async () => { - // add modlog entry - const { log: modlog } = await Moderation.createModLogEntry({ - client: this.client, - type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, - user: user, - moderator: moderator.id, - reason: options.reason, - duration: options.duration, - guild: this, - evidence: options.evidence - }); - if (!modlog) return banResponse.MODLOG_ERROR; - caseID = modlog.id; - - // dm user - dmSuccessEvent = await Moderation.punishDM({ - client: this.client, - modlog: modlog.id, - guild: this, - user: user, - punishment: 'banned', - duration: options.duration ?? 0, - reason: options.reason ?? undefined, - sendFooter: true - }); - - // ban - const banSuccess = await this.bans - .create(user?.id ?? options.user, { - reaso