aboutsummaryrefslogtreecommitdiff
path: root/src/lib/extensions
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/extensions
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/extensions')
-rw-r--r--src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts586
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts586
-rw-r--r--src/lib/extensions/discord-akairo/BushCommandHandler.ts37
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitor.ts19
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitorHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushListener.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushListenerHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushTask.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushTaskHandler.ts3
-rw-r--r--src/lib/extensions/discord-akairo/SlashMessage.ts3
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.ts200
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuild.ts916
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuildMember.ts1255
-rw-r--r--src/lib/extensions/discord.js/ExtendedMessage.ts12
-rw-r--r--src/lib/extensions/discord.js/ExtendedUser.ts35
-rw-r--r--src/lib/extensions/global.ts13
17 files changed, 0 insertions, 3680 deletions
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<R = unknown> = (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<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
- off<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- removeAllListeners<K extends keyof BushClientEvents>(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<Ready extends boolean = boolean> extends AkairoClient<Ready> {
- 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<Ready, string, string | null>;
-
- /* =-=-= 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 <<v17.x.x>>, not <<${process.version}>>.`, false));
- process.exit(2);
- }
-
- this.setMaxListeners(20);
-
- this.perspective = await google.discoverAPI<any>('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: <ArgumentTypeCaster>duration,
- contentWithDuration: <ArgumentTypeCaster>contentWithDuration,
- permission: <ArgumentTypeCaster>permission,
- snowflake: <ArgumentTypeCaster>snowflake,
- discordEmoji: <ArgumentTypeCaster>discordEmoji,
- roleWithDuration: <ArgumentTypeCaster>roleWithDuration,
- abbreviatedNumber: <ArgumentTypeCaster>abbreviatedNumber,
- durationSeconds: <ArgumentTypeCaster>durationSeconds,
- globalUser: <ArgumentTypeCaster>globalUser,
- messageLink: <ArgumentTypeCaster>messageLink,
- tinyColor: <ArgumentTypeCaster>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 <<Sentry>>.`, 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 <<instance database>>.`, false);
- } catch (e) {
- await this.console.error(
- 'startup',
- `Failed to connect to <<instance database>> 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 <<shared database>>.`, false);
- } catch (e) {
- await this.console.error(
- 'startup',
- `Failed to connect to <<shared database>> 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 <<cache>>.`, 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<string> {
- 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<Ready extends boolean = boolean> extends EventEmitter, PatchedElements, AkairoClient<Ready> {
- on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
- off<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
- removeAllListeners<K extends keyof BushClientEvents>(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<ArgumentOptions, 'type' | 'prompt'>, 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> | 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<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'>,
- 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<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>;
-
-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<CommandOptions & ExtendedCommandOptions> = {};
- 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<T extends keyof PseudoArguments> = NonNullable<PseudoArguments[T]>;
-export type OptArgType<T extends keyof PseudoArguments> = 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<Message>;
-};
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<string, BushCommand>;
- public declare categories: Collection<string, Category<string, BushCommand>>;
-}
-
-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<Snowflake, Message>
- ];
- 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<Snowflake, boolean>,
- all?: boolean
- ];
- bushUnlockdown: [
- moderator: GuildMember,
- reason: string | undefined,
- channelsSuccessMap: Collection<Snowflake, boolean>,
- all?: boolean
- ];
- massBan: [
- moderator: GuildMember,
- guild: Guild,
- reason: string | undefined,
- results: Collection<Snowflake, BanResponse>
- ];
- 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<boolean>;
- /**
- * 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<GuildDB['enabledFeatures']>;
- /**
- * 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<GuildDB['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
- */
- toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>;
- /**
- * Fetches a custom setting for the guild
- * @param setting The setting to get
- */
- getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]>;
- /**
- * 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<K extends Exclude<keyof GuildModel, 'id'>>(
- setting: K,
- value: GuildModel[K],
- moderator?: GuildMember
- ): Promise<GuildModel>;
- /**
- * 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<TextChannel | 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}
- */
- sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise<Message | null | undefined>;
- /**
- * 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<void>;
- /**
- * 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<BanResponse>;
- /**
- * {@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<BanResponse>;
- /**
- * 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<UnbanResponse>;
- /**
- * Denies send permissions in specified channels
- * @param options The options for locking down the guild
- */
- lockdown(options: LockdownOptions): Promise<LockdownResponse>;
- quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null>;
- }
-}
-
-/**
- * Represents a guild (or a server) on Discord.
- * <info>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}.</info>
- */
-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<boolean> {
- 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<GuildModel['enabledFeatures']> {
- 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<GuildModel['enabledFeatures']> {
- 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<GuildModel['enabledFeatures']> {
- 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<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> {
- 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<K extends Exclude<keyof GuildModel, 'id'>>(
- setting: K,
- value: GuildDB[K],
- moderator?: GuildMember
- ): Promise<GuildDB> {
- 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<TextChannel | undefined> {
- 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<Message | null | undefined> {
- 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> {
- 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<BanResponse> {
- // 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, {
- reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`,
- deleteMessageDays: options.deleteDays
- })
- .catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: user,
- guild: this,
- duration: options.duration,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccessEvent) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
-
- if (!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret))
- this.client.emit(
- 'bushBan',
- user,
- moderator,
- this,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent,
- options.evidence
- );
- return ret;
- }
-
- /**
- * {@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
- */
- public override async massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse> {
- if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED;
-
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntrySimple({
- client: this.client,
- type: ModLogType.PERM_BAN,
- user: options.user,
- moderator: options.moderator,
- reason: options.reason,
- duration: 0,
- guild: this.id
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
-
- let dmSuccessEvent: boolean | undefined = undefined;
- // dm user
- if (this.members.cache.has(options.user)) {
- dmSuccessEvent = await Moderation.punishDM({
- client: this.client,
- modlog: modlog.id,
- guild: this,
- user: options.user,
- punishment: 'banned',
- duration: 0,
- reason: options.reason ?? undefined,
- sendFooter: true
- });
- }
-
- // ban
- const banSuccess = await this.bans
- .create(options.user, {
- reason: `${options.moderator} | ${options.reason}`,
- deleteMessageDays: options.deleteDays
- })
- .catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: options.user,
- guild: this,
- duration: 0,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccessEvent) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
- return ret;
- }
-
- /**
- * 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.
- */
- public override async bushUnban(options: GuildBushUnbanOptions): Promise<UnbanResponse> {
- // checks
- if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.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 unbanResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- const bans = await this.bans.fetch();
-
- let notBanned = false;
- if (!bans.has(user.id)) notBanned = true;
-
- const unbanSuccess = await this.bans
- .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch((e) => {
- if (e?.code === 'UNKNOWN_BAN') {
- notBanned = true;
- return true;
- } else return false;
- });
-
- if (notBanned) return unbanResponse.NOT_BANNED;
- if (!unbanSuccess) return unbanResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.UNBAN,
- user: user.id,
- moderator: moderator.id,
- reason: options.reason,
- guild: this,
- evidence: options.evidence
- });
- if (!modlog) return unbanResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove punishment entry
- const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'ban',
- user: user.id,
- guild: this
- });
- if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
-
- // dm user
- dmSuccessEvent = await Moderation.punishDM({
- client: this.client,
- guild: this,
- user: user,
- punishment: 'unbanned',
- reason: options.reason ?? undefined,
- sendFooter: false
- });
-
- if (!dmSuccessEvent) return unbanResponse.DM_ERROR;
- return unbanResponse.SUCCESS;
- })();
- if (
- !([unbanResponse.ACTION_ERROR, unbanResponse.MODLOG_ERROR, unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const).includes(
- ret
- )
- )
- this.client.emit(
- 'bushUnban',
- user,
- moderator,
- this,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Denies send permissions in specified channels
- * @param options The options for locking down the guild
- */
- public override async lockdown(options: LockdownOptions): Promise<LockdownResponse> {
- if (!options.all && !options.channel) return 'all not chosen and no channel specified';
- const channelIds = options.all ? await this.getSetting('lockdownChannels') : [options.channel!.id];
-
- if (!channelIds.length) return 'no channels configured';
- const mappedChannels = channelIds.map((id) => this.channels.cache.get(id));
-
- const invalidChannels = mappedChannels.filter((c) => c === undefined);
- if (invalidChannels.length) return `invalid channel configured: ${invalidChannels.join(', ')}`;
-
- const moderator = this.members.resolve(options.moderator);
- if (!moderator) return 'moderator not found';
-
- const errors = new Collection<Snowflake, Error>();
- const success = new Collection<Snowflake, boolean>();
- const ret = await (async (): Promise<LockdownResponse> => {
- for (const _channel of mappedChannels) {
- const channel = _channel!;
- if (!channel.isTextBased()) {
- errors.set(channel.id, new Error('wrong channel type'));
- success.set(channel.id, false);
- continue;
- }
- if (!channel.permissionsFor(this.members.me!.id)?.has([PermissionFlagsBits.ManageChannels])) {
- errors.set(channel.id, new Error('client no permission'));
- success.set(channel.id, false);
- continue;
- } else if (!channel.permissionsFor(moderator)?.has([PermissionFlagsBits.ManageChannels])) {
- errors.set(channel.id, new Error('moderator no permission'));
- success.set(channel.id, false);
- continue;
- }
-
- const reason = `[${options.unlock ? 'Unlockdown' : 'Lockdown'}] ${moderator.user.tag} | ${
- options.reason ?? 'No reason provided'
- }`;
-
- const permissionOverwrites = channel.isThread() ? channel.parent!.permissionOverwrites : channel.permissionOverwrites;
- const perms = {
- SendMessagesInThreads: options.unlock ? null : false,
- SendMessages: options.unlock ? null : false
- };
- const permsForMe = {
- [channel.isThread() ? 'SendMessagesInThreads' : 'SendMessages']: options.unlock ? null : true
- }; // so I can send messages in the channel
-
- const changePermSuccess = await permissionOverwrites.edit(this.id, perms, { reason }).catch((e) => e);
- if (changePermSuccess instanceof Error) {
- errors.set(channel.id, changePermSuccess);
- success.set(channel.id, false);
- } else {
- success.set(channel.id, true);
- await permissionOverwrites.edit(this.members.me!, permsForMe, { reason });
- await channel.send({
- embeds: [
- {
- author: { name: moderator.user.tag, icon_url: moderator.displayAvatarURL() },
- title: `This channel has been ${options.unlock ? 'un' : ''}locked`,
- description: options.reason ?? 'No reason provided',
- color: options.unlock ? colors.Green : colors.Red,
- timestamp: new Date().toISOString()
- }
- ]
- });
- }
- }
-
- if (errors.size) return errors;
- else return `success: ${success.filter((c) => c === true).size}`;
- })();
-
- this.client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all);
- return ret;
- }
-
- public override async quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null> {
- if (!channel.isTextBased() || channel.isDMBased() || channel.guildId !== this.id || !this.members.me) return null;
- if (!channel.permissionsFor(this.members.me).has('ManageWebhooks')) return null;
-
- const quote = new Message(this.client, rawQuote);
-
- const target = channel instanceof ThreadChannel ? channel.parent : channel;
- if (!target) return null;
-
- const webhooks: Collection<string, Webhook> = await target.fetchWebhooks().catch((e) => e);
- if (!(webhooks instanceof Collection)) return null;
-
- // find a webhook that we can use
- let webhook = webhooks.find((w) => !!w.token) ?? null;
- if (!webhook)
- webhook = await target
- .createWebhook({
- name: `${this.client.user!.username} Quotes #${target.name}`,
- avatar: this.client.user!.displayAvatarURL({ size: 2048 }),
- reason: 'Creating a webhook for quoting'
- })
- .catch(() => null);
-
- if (!webhook) return null;
-
- const sendOptions: Omit<WebhookMessageOptions, 'flags'> = {};
-
- const displayName = quote.member?.displayName ?? quote.author.username;
-
- switch (quote.type) {
- case MessageType.Default:
- case MessageType.Reply:
- case MessageType.ChatInputCommand:
- case MessageType.ContextMenuCommand:
- case MessageType.ThreadStarterMessage:
- sendOptions.content = quote.content || undefined;
- sendOptions.threadId = channel instanceof ThreadChannel ? channel.id : undefined;
- sendOptions.embeds = quote.embeds.length ? quote.embeds : undefined;
- //@ts-expect-error: jank
- sendOptions.attachments = quote.attachments.size
- ? [...quote.attachments.values()].map((a) => AttachmentBuilder.from(a as JSONEncodable<AttachmentPayload>))
- : undefined;
-
- if (quote.stickers.size && !(quote.content || quote.embeds.length || quote.attachments.size))
- sendOptions.content = '[[This message has a sticker but not content]]';
-
- break;
- case MessageType.RecipientAdd: {
- const recipient = rawQuote.mentions[0];
- if (!recipient) {
- sendOptions.content = `${emojis.error} Cannot resolve recipient.`;
- break;
- }
-
- if (quote.channel.isThread()) {
- const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username;
- sendOptions.content = `${emojis.join} ${displayName} added ${recipientDisplay} to the thread.`;
- } else {
- // this should never happen
- sendOptions.content = `${emojis.join} ${displayName} added ${recipient.username} to the group.`;
- }
-
- break;
- }
- case MessageType.RecipientRemove: {
- const recipient = rawQuote.mentions[0];
- if (!recipient) {
- sendOptions.content = `${emojis.error} Cannot resolve recipient.`;
- break;
- }
-
- if (quote.channel.isThread()) {
- const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username;
- sendOptions.content = `${emojis.leave} ${displayName} removed ${recipientDisplay} from the thread.`;
- } else {
- // this should never happen
- sendOptions.content = `${emojis.leave} ${displayName} removed ${recipient.username} from the group.`;
- }
-
- break;
- }
-
- case MessageType.ChannelNameChange:
- sendOptions.content = `<:pencil:957988608994861118> ${displayName} changed the channel name: **${quote.content}**`;
-
- break;
-
- case MessageType.ChannelPinnedMessage:
- throw new Error('Not implemented yet: MessageType.ChannelPinnedMessage case');
- case MessageType.UserJoin: {
- const messages = [
- '{username} joined the party.',
- '{username} is here.',
- 'Welcome, {username}. We hope you brought pizza.',
- 'A wild {username} appeared.',
- '{username} just landed.',
- '{username} just slid into the server.',
- '{username} just showed up!',
- 'Welcome {username}. Say hi!',
- '{username} hopped into the server.',
- 'Everyone welcome {username}!',
- "Glad you're here, {username}.",
- 'Good to see you, {username}.',
- 'Yay you made it, {username}!'
- ];
-
- const timestamp = SnowflakeUtil.timestampFrom(quote.id);
-
- // this is the same way that the discord client decides what message to use.
- const message = messages[timestamp % messages.length].replace(/{username}/g, displayName);
-
- sendOptions.content = `${emojis.join} ${message}`;
- break;
- }
- case MessageType.GuildBoost:
- sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${
- quote.content ? ` **${quote.content}** times` : ''
- }!`;
-
- break;
- case MessageType.GuildBoostTier1:
- case MessageType.GuildBoostTier2:
- case MessageType.GuildBoostTier3:
- sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${
- quote.content ? ` **${quote.content}** times` : ''
- }! ${quote.guild?.name} has achieved **Level ${quote.type - 8}!**`;
-
- break;
- case MessageType.ChannelFollowAdd:
- sendOptions.content = `${displayName} has added **${quote.content}** to this channel. Its most important updates will show up here.`;
-
- break;
- case MessageType.GuildDiscoveryDisqualified:
- sendOptions.content =
- '<:SystemMessageCross:842172192418693173> This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details.';
-
- break;
- case MessageType.GuildDiscoveryRequalified:
- sendOptions.content =
- '<:SystemMessageCheck:842172191801212949> This server is eligible for Server Discovery again and has been automatically relisted!';
-
- break;
- case MessageType.GuildDiscoveryGracePeriodInitialWarning:
- sendOptions.content =
- '<:SystemMessageWarn:842172192401915971> This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery.';
-
- break;
- case MessageType.GuildDiscoveryGracePeriodFinalWarning:
- sendOptions.content =
- '<:SystemMessageWarn:842172192401915971> This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.';
-
- break;
- case MessageType.ThreadCreated: {
- const threadId = rawQuote.message_reference?.channel_id;
-
- sendOptions.content = `<:thread:865033845753249813> ${displayName} started a thread: **[${quote.content}](https://discord.com/channels/${quote.guildId}/${threadId}
- )**. See all threads.`;
-
- break;
- }
- case MessageType.GuildInviteReminder:
- sendOptions.content = 'Wondering who to invite? Start by inviting anyone who can help you build the server!';
-
- break;
- // todo: use enum for this
- case 24 as MessageType: {
- const embed = quote.embeds[0];
- // eslint-disable-next-line deprecation/deprecation
- assert.equal(embed.data.type, 'auto_moderation_message');
- const ruleName = embed.fields!.find((f) => f.name === 'rule_name')!.value;
- const channelId = embed.fields!.find((f) => f.name === 'channel_id')!.value;
- const keyword = embed.fields!.find((f) => f.name === 'keyword')!.value;
-
- sendOptions.username = `AutoMod (${quote.member?.displayName ?? quote.author.username})`;
- sendOptions.content = `Automod has blocked a message in <#${channelId}>`;
- sendOptions.embeds = [
- {
- title: quote.member?.displayName ?? quote.author.username,
- description: embed.description ?? 'There is no content???',
- footer: {
- text: `Keyword: ${keyword} • Rule: ${ruleName}`
- },
- color: 0x36393f
- }
- ];
-
- break;
- }
- case MessageType.ChannelIconChange:
- case MessageType.Call:
- default:
- sendOptions.content = `${emojis.error} I cannot quote messages of type **${
- MessageType[quote.type] || quote.type
- }** messages, please report this to my developers.`;
-
- break;
- }
-
- sendOptions.allowedMentions = AllowedMentions.none();
- sendOptions.username ??= quote.member?.displayName ?? quote.author.username;
- sendOptions.avatarURL = quote.member?.displayAvatarURL({ size: 2048 }) ?? quote.author.displayAvatarURL({ size: 2048 });
-
- return await webhook.send(sendOptions); /* .catch((e: any) => e); */
- }
-}
-
-/**
- * Options for unbanning a user
- */
-export interface GuildBushUnbanOptions {
- /**
- * The user to unban
- */
- user: UserResolvable | User;
-
- /**
- * The reason for unbanning the user
- */
- reason?: string | null;
-
- /**
- * The moderator who unbanned the user
- */
- moderator?: UserResolvable;
-
- /**
- * The evidence for the unban
- */
- evidence?: string;
-}
-
-export interface GuildMassBanOneOptions {
- /**
- * The user to ban
- */
- user: Snowflake;
-
- /**
- * The reason to ban the user
- */
- reason: string;
-
- /**
- * The moderator who banned the user
- */
- moderator: Snowflake;
-
- /**
- * The number of days to delete the user's messages for
- */
- deleteDays?: number;
-}
-
-/**
- * Options for banning a user
- */
-export interface GuildBushBanOptions {
- /**
- * The user to ban
- */
- user: UserResolvable;
-
- /**
- * The reason to ban the user
- */
- reason?: string | null;
-
- /**
- * The moderator who banned the user
- */
- moderator?: UserResolvable;
-
- /**
- * The duration of the ban
- */
- duration?: number;
-
- /**
- * The number of days to delete the user's messages for
- */
- deleteDays?: number;
-
- /**
- * The evidence for the ban
- */
- evidence?: string;
-}
-
-type ValueOf<T> = T[keyof T];
-
-export const unbanResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...punishmentEntryRemove,
- NOT_BANNED: 'user not banned'
-} as const);
-
-/**
- * Response returned when unbanning a user
- */
-export type UnbanResponse = ValueOf<typeof unbanResponse>;
-
-/**
- * Options for locking down channel(s)
- */
-export interface LockdownOptions {
- /**
- * The moderator responsible for the lockdown
- */
- moderator: GuildMemberResolvable;
-
- /**
- * Whether to lock down all (specified) channels
- */
- all: boolean;
-
- /**
- * Reason for the lockdown
- */
- reason?: string;
-
- /**
- * A specific channel to lockdown
- */
- channel?: ThreadChannel | NewsChannel | TextChannel | VoiceChannel;
-
- /**
- * Whether or not to unlock the channel(s) instead of locking them
- */
- unlock?: boolean;
-}
-
-/**
- * Response returned when locking down a channel
- */
-export type LockdownResponse =
- | `success: ${number}`
- | 'all not chosen and no channel specified'
- | 'no channels configured'
- | `invalid channel configured: ${string}`
- | 'moderator not found'
- | Collection<string, Error>;
diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
deleted file mode 100644
index f8add83..0000000
--- a/src/lib/extensions/discord.js/ExtendedGuildMember.ts
+++ /dev/null
@@ -1,1255 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import { formatError, Moderation, ModLogType, Time, type BushClientEvents, type PunishmentTypeDM, type ValueOf } from '#lib';
-import {
- ChannelType,
- GuildMember,
- PermissionFlagsBits,
- type GuildChannelResolvable,
- type GuildTextBasedChannel,
- type Role
-} from 'discord.js';
-/* eslint-enable @typescript-eslint/no-unused-vars */
-
-declare module 'discord.js' {
- export interface GuildMember {
- /**
- * Send a punishment dm to the user.
- * @param punishment The punishment that the user has received.
- * @param reason The reason for the user's punishment.
- * @param duration The duration of the punishment.
- * @param modlog The modlog case id so the user can make an appeal.
- * @param sendFooter Whether or not to send the guild's punishment footer with the dm.
- * @returns Whether or not the dm was sent successfully.
- */
- bushPunishDM(
- punishment: PunishmentTypeDM,
- reason?: string | null,
- duration?: number,
- modlog?: string,
- sendFooter?: boolean
- ): Promise<boolean>;
- /**
- * Warn the user, create a modlog entry, and send a dm to the user.
- * @param options Options for warning the user.
- * @returns An object with the result of the warning, and the case number of the warn.
- * @emits {@link BushClientEvents.bushWarn}
- */
- bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }>;
- /**
- * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment.
- * @param options Options for adding a role to the user.
- * @returns A status message for adding the add.
- * @emits {@link BushClientEvents.bushPunishRole}
- */
- bushAddRole(options: AddRoleOptions): Promise<AddRoleResponse>;
- /**
- * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment.
- * @param options Options for removing a role from the user.
- * @returns A status message for removing the role.
- * @emits {@link BushClientEvents.bushPunishRoleRemove}
- */
- bushRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse>;
- /**
- * Mute the user, create a modlog entry, creates a punishment entry, and dms the user.
- * @param options Options for muting the user.
- * @returns A status message for muting the user.
- * @emits {@link BushClientEvents.bushMute}
- */
- bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse>;
- /**
- * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user.
- * @param options Options for unmuting the user.
- * @returns A status message for unmuting the user.
- * @emits {@link BushClientEvents.bushUnmute}
- */
- bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse>;
- /**
- * Kick the user, create a modlog entry, and dm the user.
- * @param options Options for kicking the user.
- * @returns A status message for kicking the user.
- * @emits {@link BushClientEvents.bushKick}
- */
- bushKick(options: BushPunishmentOptions): Promise<KickResponse>;
- /**
- * Ban the user, create a modlog entry, create a punishment entry, and dm the user.
- * @param options Options for banning the user.
- * @returns A status message for banning the user.
- * @emits {@link BushClientEvents.bushBan}
- */
- bushBan(options: BushBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>>;
- /**
- * Prevents a user from speaking in a channel.
- * @param options Options for blocking the user.
- */
- bushBlock(options: BlockOptions): Promise<BlockResponse>;
- /**
- * Allows a user to speak in a channel.
- * @param options Options for unblocking the user.
- */
- bushUnblock(options: UnblockOptions): Promise<UnblockResponse>;
- /**
- * Mutes a user using discord's timeout feature.
- * @param options Options for timing out the user.
- */
- bushTimeout(options: BushTimeoutOptions): Promise<TimeoutResponse>;
- /**
- * Removes a timeout from a user.
- * @param options Options for removing the timeout.
- */
- bushRemoveTimeout(options: BushPunishmentOptions): Promise<RemoveTimeoutResponse>;
- /**
- * Whether or not the user is an owner of the bot.
- */
- isOwner(): boolean;
- /**
- * Whether or not the user is a super user of the bot.
- */
- isSuperUser(): boolean;
- }
-}
-
-/**
- * Represents a member of a guild on Discord.
- */
-export class ExtendedGuildMember extends GuildMember {
- /**
- * Send a punishment dm to the user.
- * @param punishment The punishment that the user has received.
- * @param reason The reason for the user's punishment.
- * @param duration The duration of the punishment.
- * @param modlog The modlog case id so the user can make an appeal.
- * @param sendFooter Whether or not to send the guild's punishment footer with the dm.
- * @returns Whether or not the dm was sent successfully.
- */
- public override async bushPunishDM(
- punishment: PunishmentTypeDM,
- reason?: string | null,
- duration?: number,
- modlog?: string,
- sendFooter = true
- ): Promise<boolean> {
- return Moderation.punishDM({
- client: this.client,
- modlog,
- guild: this.guild,
- user: this,
- punishment,
- reason: reason ?? undefined,
- duration,
- sendFooter
- });
- }
-
- /**
- * Warn the user, create a modlog entry, and send a dm to the user.
- * @param options Options for warning the user.
- * @returns An object with the result of the warning, and the case number of the warn.
- * @emits {@link BushClientEvents.bushWarn}
- */
- public override async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> {
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return { result: warnResponse.CANNOT_RESOLVE_USER, caseNum: null };
-
- const ret = await (async (): Promise<{ result: WarnResponse; caseNum: number | null }> => {
- // add modlog entry
- const result = await Moderation.createModLogEntry(
- {
- client: this.client,
- type: ModLogType.WARN,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- },
- true
- );
- caseID = result.log?.id;
- if (!result || !result.log) return { result: warnResponse.MODLOG_ERROR, caseNum: null };
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('warned', options.reason);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return { result: warnResponse.DM_ERROR, caseNum: result.caseNum };
- }
-
- return { result: warnResponse.SUCCESS, caseNum: result.caseNum };
- })();
- if (!([warnResponse.MODLOG_ERROR] as const).includes(ret.result) && !options.silent)
- this.client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!);
- return ret;
- }
-
- /**
- * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment.
- * @param options Options for adding a role to the user.
- * @returns A status message for adding the add.
- * @emits {@link BushClientEvents.bushPunishRole}
- */
- public override async bushAddRole(options: AddRoleOptions): Promise<AddRoleResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return addRoleResponse.MISSING_PERMISSIONS;
- const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
- if (ifShouldAddRole !== true) return ifShouldAddRole;
-
- let caseID: string | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return addRoleResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- if (options.addToModlog || options.duration) {
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE,
- guild: this.guild,
- moderator: moderator.id,
- user: this,
- reason: 'N/A',
- pseudo: !options.addToModlog,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return addRoleResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (options.addToModlog || options.duration) {
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'role',
- user: this,
- guild: this.guild,
- modlog: modlog.id,
- duration: options.duration,
- extraInfo: options.role.id
- });
- if (!punishmentEntrySuccess) return addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR;
- }
- }
-
- const removeRoleSuccess = await this.roles.add(options.role, `${moderator.tag}`);
- if (!removeRoleSuccess) return addRoleResponse.ACTION_ERROR;
-
- return addRoleResponse.SUCCESS;
- })();
- if (
- !(
- [addRoleResponse.ACTION_ERROR, addRoleResponse.MODLOG_ERROR, addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const
- ).includes(ret) &&
- options.addToModlog &&
- !options.silent
- )
- this.client.emit(
- 'bushPunishRole',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- options.role,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment.
- * @param options Options for removing a role from the user.
- * @returns A status message for removing the role.
- * @emits {@link BushClientEvents.bushPunishRoleRemove}
- */
- public override async bushRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return removeRoleResponse.MISSING_PERMISSIONS;
- const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
- if (ifShouldAddRole !== true) return ifShouldAddRole;
-
- let caseID: string | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return removeRoleResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- if (options.addToModlog) {
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.REMOVE_PUNISHMENT_ROLE,
- guild: this.guild,
- moderator: moderator.id,
- user: this,
- reason: 'N/A',
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return removeRoleResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'role',
- user: this,
- guild: this.guild,
- extraInfo: options.role.id
- });
-
- if (!punishmentEntrySuccess) return removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
- }
-
- const removeRoleSuccess = await this.roles.remove(options.role, `${moderator.tag}`);
- if (!removeRoleSuccess) return removeRoleResponse.ACTION_ERROR;
-
- return removeRoleResponse.SUCCESS;
- })();
-
- if (
- !(
- [
- removeRoleResponse.ACTION_ERROR,
- removeRoleResponse.MODLOG_ERROR,
- removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR
- ] as const
- ).includes(ret) &&
- options.addToModlog &&
- !options.silent
- )
- this.client.emit(
- 'bushPunishRoleRemove',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.role,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Check whether or not a role should be added/removed from the user based on hierarchy.
- * @param role The role to check if can be modified.
- * @param moderator The moderator that is trying to add/remove the role.
- * @returns `true` if the role should be added/removed or a string for the reason why it shouldn't.
- */
- #checkIfShouldAddRole(
- role: Role | Role,
- moderator?: GuildMember
- ): true | 'user hierarchy' | 'role managed' | 'client hierarchy' {
- if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) {
- return shouldAddRoleResponse.USER_HIERARCHY;
- } else if (role.managed) {
- return shouldAddRoleResponse.ROLE_MANAGED;
- } else if (this.guild.members.me!.roles.highest.position <= role.position) {
- return shouldAddRoleResponse.CLIENT_HIERARCHY;
- }
- return true;
- }
-
- /**
- * Mute the user, create a modlog entry, creates a punishment entry, and dms the user.
- * @param options Options for muting the user.
- * @returns A status message for muting the user.
- * @emits {@link BushClientEvents.bushMute}
- */
- public override async bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
- // checks
- const checks = await Moderation.checkMutePermissions(this.guild);
- if (checks !== true) return checks;
-
- const muteRoleID = (await this.guild.getSetting('muteRole'))!;
- const muteRole = this.guild.roles.cache.get(muteRoleID)!;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return muteResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // add role
- const muteSuccess = await this.roles
- .add(muteRole, `[Mute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch(async (e) => {
- await this.client.console.warn('muteRoleAddError', e);
- this.client.console.debug(e);
- return false;
- });
- if (!muteSuccess) return muteResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return muteResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // add punishment entry so they can be unmuted later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'mute',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id
- });
-
- if (!punishmentEntrySuccess) return muteResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('muted', options.reason, options.duration ?? 0, modlog.id);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return muteResponse.DM_ERROR;
- }
-
- return muteResponse.SUCCESS;
- })();
-
- if (
- !([muteResponse.ACTION_ERROR, muteResponse.MODLOG_ERROR, muteResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushMute',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user.
- * @param options Options for unmuting the user.
- * @returns A status message for unmuting the user.
- * @emits {@link BushClientEvents.bushUnmute}
- */
- public override async bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
- // checks
- const checks = await Moderation.checkMutePermissions(this.guild);
- if (checks !== true) return checks;
-
- const muteRoleID = (await this.guild.getSetting('muteRole'))!;
- const muteRole = this.guild.roles.cache.get(muteRoleID)!;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return unmuteResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // remove role
- const muteSuccess = await this.roles
- .remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
- .catch(async (e) => {
- await this.client.console.warn('muteRoleAddError', formatError(e, true));
- return false;
- });
- if (!muteSuccess) return unmuteResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.UNMUTE,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return unmuteResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove mute entry
- const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'mute',
- user: this,
- guild: this.guild
- });
-
- if (!removePunishmentEntrySuccess) return unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('unmuted', options.reason, undefined, '', false);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return unmuteResponse.DM_ERROR;
- }
-
- return unmuteResponse.SUCCESS;
- })();
-
- if (
- !(
- [unmuteResponse.ACTION_ERROR, unmuteResponse.MODLOG_ERROR, unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const
- ).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushUnmute',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Kick the user, create a modlog entry, and dm the user.
- * @param options Options for kicking the user.
- * @returns A status message for kicking the user.
- * @emits {@link BushClientEvents.bushKick}
- */
- public override async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
- // checks
- if (!this.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers) || !this.kickable)
- return kickResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return kickResponse.CANNOT_RESOLVE_USER;
- const ret = await (async () => {
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.KICK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return kickResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // dm user
- const dmSuccess = options.silent ? null : await this.bushPunishDM('kicked', options.reason, undefined, modlog.id);
- dmSuccessEvent = dmSuccess ?? undefined;
-
- // kick
- const kickSuccess = await this.kick(`${moderator?.tag} | ${options.reason ?? 'No reason provided.'}`).catch(() => false);
- if (!kickSuccess) return kickResponse.ACTION_ERROR;
-
- if (dmSuccess === false) return kickResponse.DM_ERROR;
- return kickResponse.SUCCESS;
- })();
- if (!([kickResponse.ACTION_ERROR, kickResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushKick',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Ban the user, create a modlog entry, create a punishment entry, and dm the user.
- * @param options Options for banning the user.
- * @returns A status message for banning the user.
- * @emits {@link BushClientEvents.bushBan}
- */
- public override async bushBan(options: BushBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.BanMembers) || !this.bannable)
- return banResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return banResponse.CANNOT_RESOLVE_USER;
-
- // ignore result, they should still be banned even if their mute cannot be removed
- await this.bushUnmute({
- reason: 'User is about to be banned, a mute is no longer necessary.',
- moderator: this.guild.members.me!,
- silent: true
- });
-
- 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: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await this.bushPunishDM('banned', options.reason, options.duration ?? 0, modlog.id);
- dmSuccessEvent = dmSuccess ?? undefined;
-
- // ban
- const banSuccess = await this.ban({
- reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`,
- deleteMessageDays: options.deleteDays
- }).catch(() => false);
- if (!banSuccess) return banResponse.ACTION_ERROR;
-
- // add punishment entry so they can be unbanned later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'ban',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id
- });
- if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- if (!dmSuccess) return banResponse.DM_ERROR;
- return banResponse.SUCCESS;
- })();
- if (
- !([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushBan',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Prevents a user from speaking in a channel.
- * @param options Options for blocking the user.
- */
- public override async bushBlock(options: BlockOptions): Promise<BlockResponse> {
- const channel = this.guild.channels.resolve(options.channel);
- if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.INVALID_CHANNEL;
-
- // checks
- if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels))
- return blockResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return blockResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // change channel permissions
- const channelToUse = channel.isThread() ? channel.parent! : channel;
- const perm = channel.isThread() ? { SendMessagesInThreads: false } : { SendMessages: false };
- const blockSuccess = await channelToUse.permissionOverwrites
- .edit(this, perm, { reason: `[Block] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` })
- .catch(() => false);
- if (!blockSuccess) return blockResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: options.duration ? ModLogType.TEMP_CHANNEL_BLOCK : ModLogType.PERM_CHANNEL_BLOCK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return blockResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // add punishment entry so they can be unblocked later
- const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
- client: this.client,
- type: 'block',
- user: this,
- guild: this.guild,
- duration: options.duration,
- modlog: modlog.id,
- extraInfo: channel.id
- });
- if (!punishmentEntrySuccess) return blockResponse.PUNISHMENT_ENTRY_ADD_ERROR;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await Moderation.punishDM({
- client: this.client,
- punishment: 'blocked',
- reason: options.reason ?? undefined,
- duration: options.duration ?? 0,
- modlog: modlog.id,
- guild: this.guild,
- user: this,
- sendFooter: true,
- channel: channel.id
- });
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return blockResponse.DM_ERROR;
-
- return blockResponse.SUCCESS;
- })();
-
- if (
- !([blockResponse.ACTION_ERROR, blockResponse.MODLOG_ERROR, blockResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(
- ret
- ) &&
- !options.silent
- )
- this.client.emit(
- 'bushBlock',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- channel,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Allows a user to speak in a channel.
- * @param options Options for unblocking the user.
- */
- public override async bushUnblock(options: UnblockOptions): Promise<UnblockResponse> {
- const _channel = this.guild.channels.resolve(options.channel);
- if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL;
- const channel = _channel as GuildTextBasedChannel;
-
- // checks
- if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels))
- return unblockResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return unblockResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // change channel permissions
- const channelToUse = channel.isThread() ? channel.parent! : channel;
- const perm = channel.isThread() ? { SendMessagesInThreads: null } : { SendMessages: null };
- const blockSuccess = await channelToUse.permissionOverwrites
- .edit(this, perm, { reason: `[Unblock] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` })
- .catch(() => false);
- if (!blockSuccess) return unblockResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.CHANNEL_UNBLOCK,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
- if (!modlog) return unblockResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- // remove punishment entry
- const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
- client: this.client,
- type: 'block',
- user: this,
- guild: this.guild,
- extraInfo: channel.id
- });
- if (!punishmentEntrySuccess) return unblockResponse.ACTION_ERROR;
-
- // dm user
- const dmSuccess = options.silent
- ? null
- : await Moderation.punishDM({
- client: this.client,
- punishment: 'unblocked',
- reason: options.reason ?? undefined,
- guild: this.guild,
- user: this,
- sendFooter: false,
- channel: channel.id
- });
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return blockResponse.DM_ERROR;
-
- dmSuccessEvent = !!dmSuccess;
- if (!dmSuccess) return unblockResponse.DM_ERROR;
-
- return unblockResponse.SUCCESS;
- })();
-
- if (
- !([unblockResponse.ACTION_ERROR, unblockResponse.MODLOG_ERROR, unblockResponse.ACTION_ERROR] as const).includes(ret) &&
- !options.silent
- )
- this.client.emit(
- 'bushUnblock',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- channel,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Mutes a user using discord's timeout feature.
- * @param options Options for timing out the user.
- */
- public override async bushTimeout(options: BushTimeoutOptions): Promise<TimeoutResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MISSING_PERMISSIONS;
-
- const twentyEightDays = Time.Day * 28;
- if (options.duration > twentyEightDays) return timeoutResponse.INVALID_DURATION;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return timeoutResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // timeout
- const timeoutSuccess = await this.timeout(
- options.duration,
- `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`
- ).catch(() => false);
- if (!timeoutSuccess) return timeoutResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.TIMEOUT,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return timeoutResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('timedout', options.reason, options.duration, modlog.id);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return timeoutResponse.DM_ERROR;
- }
-
- return timeoutResponse.SUCCESS;
- })();
-
- if (!([timeoutResponse.ACTION_ERROR, timeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushTimeout',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- options.duration ?? 0,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Removes a timeout from a user.
- * @param options Options for removing the timeout.
- */
- public override async bushRemoveTimeout(options: BushPunishmentOptions): Promise<RemoveTimeoutResponse> {
- // checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers))
- return removeTimeoutResponse.MISSING_PERMISSIONS;
-
- let caseID: string | undefined = undefined;
- let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
- if (!moderator) return removeTimeoutResponse.CANNOT_RESOLVE_USER;
-
- const ret = await (async () => {
- // remove timeout
- const timeoutSuccess = await this.timeout(null, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`).catch(
- () => false
- );
- if (!timeoutSuccess) return removeTimeoutResponse.ACTION_ERROR;
-
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- client: this.client,
- type: ModLogType.REMOVE_TIMEOUT,
- user: this,
- moderator: moderator.id,
- reason: options.reason,
- guild: this.guild,
- evidence: options.evidence,
- hidden: options.silent ?? false
- });
-
- if (!modlog) return removeTimeoutResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
- if (!options.silent) {
- // dm user
- const dmSuccess = await this.bushPunishDM('untimedout', options.reason, undefined, '', false);
- dmSuccessEvent = dmSuccess;
- if (!dmSuccess) return removeTimeoutResponse.DM_ERROR;
- }
-
- return removeTimeoutResponse.SUCCESS;
- })();
-
- if (!([removeTimeoutResponse.ACTION_ERROR, removeTimeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- this.client.emit(
- 'bushRemoveTimeout',
- this,
- moderator,
- this.guild,
- options.reason ?? undefined,
- caseID!,
- dmSuccessEvent!,
- options.evidence
- );
- return ret;
- }
-
- /**
- * Whether or not the user is an owner of the bot.
- */
- public override isOwner(): boolean {
- return this.client.isOwner(this);
- }
-
- /**
- * Whether or not the user is a super user of the bot.
- */
- public override isSuperUser(): boolean {
- return this.client.isSuperUser(this);
- }
-}
-
-/**
- * Options for punishing a user.
- */
-export interface BushPunishmentOptions {
- /**
- * The reason for the punishment.
- */
- reason?: string | null;
-
- /**
- * The moderator who punished the user.
- */
- moderator?: GuildMember;
-
- /**
- * Evidence for the punishment.
- */
- evidence?: string;
-
- /**
- * Makes the punishment silent by not sending the user a punishment dm and not broadcasting the event to be logged.
- */
- silent?: boolean;
-}
-
-/**
- * Punishment options for punishments that can be temporary.
- */
-export interface BushTimedPunishmentOptions extends BushPunishmentOptions {
- /**
- * The duration of the punishment.
- */
- duration?: number;
-}
-
-/**
- * Options for a role add punishment.
- */
-export interface AddRoleOptions extends BushTimedPunishmentOptions {
- /**
- * The role to add to the user.
- */
- role: Role;
-
- /**
- * Whether to create a modlog entry for this punishment.
- */
- addToModlog: boolean;
-}
-
-/**
- * Options for a role remove punishment.
- */
-export interface RemoveRoleOptions extends BushTimedPunishmentOptions {
- /**
- * The role to remove from the user.
- */
- role: Role;
-
- /**
- * Whether to create a modlog entry for this punishment.
- */
- addToModlog: boolean;
-}
-
-/**
- * Options for banning a user.
- */
-export interface BushBanOptions extends BushTimedPunishmentOptions {
- /**
- * The number of days to delete the user's messages for.
- */
- deleteDays?: number;
-}
-
-/**
- * Options for blocking a user from a channel.
- */
-export interface BlockOptions extends BushTimedPunishmentOptions {
- /**
- * The channel to block the user from.
- */
- channel: GuildChannelResolvable;
-}
-
-/**
- * Options for unblocking a user from a channel.
- */
-export interface UnblockOptions extends BushPunishmentOptions {
- /**
- * The channel to unblock the user from.
- */
- channel: GuildChannelResolvable;
-}
-
-/**
- * Punishment options for punishments that can be temporary.
- */
-export interface BushTimeoutOptions extends BushPunishmentOptions {
- /**
- * The duration of the punishment.
- */
- duration: number;
-}
-
-export const basePunishmentResponse = Object.freeze({
- SUCCESS: 'success',
- MODLOG_ERROR: 'error creating modlog entry',
- ACTION_ERROR: 'error performing action',
- CANNOT_RESOLVE_USER: 'cannot resolve user'
-} as const);
-
-export const dmResponse = Object.freeze({
- ...basePunishmentResponse,
- DM_ERROR: 'failed to dm'
-} as const);
-
-export const permissionsResponse = Object.freeze({
- MISSING_PERMISSIONS: 'missing permissions'
-} as const);
-
-export const punishmentEntryAdd = Object.freeze({
- PUNISHMENT_ENTRY_ADD_ERROR: 'error creating punishment entry'
-} as const);
-
-export const punishmentEntryRemove = Object.freeze({
- PUNISHMENT_ENTRY_REMOVE_ERROR: 'error removing punishment entry'
-} as const);
-
-export const shouldAddRoleResponse = Object.freeze({
- USER_HIERARCHY: 'user hierarchy',
- CLIENT_HIERARCHY: 'client hierarchy',
- ROLE_MANAGED: 'role managed'
-} as const);
-
-export const baseBlockResponse = Object.freeze({
- INVALID_CHANNEL: 'invalid channel'
-} as const);
-
-export const baseMuteResponse = Object.freeze({
- NO_MUTE_ROLE: 'no mute role',
- MUTE_ROLE_INVALID: 'invalid mute role',
- MUTE_ROLE_NOT_MANAGEABLE: 'mute role not manageable'
-} as const);
-
-export const warnResponse = Object.freeze({
- ...dmResponse
-} as const);
-
-export const addRoleResponse = Object.freeze({
- ...basePunishmentResponse,
- ...permissionsResponse,
- ...shouldAddRoleResponse,
- ...punishmentEntryAdd
-} as const);
-
-export const removeRoleResponse = Object.freeze({
- ...basePunishmentResponse,
- ...permissionsResponse,
- ...shouldAddRoleResponse,
- ...punishmentEntryRemove
-} as const);
-
-export const muteResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseMuteResponse,
- ...punishmentEntryAdd
-} as const);
-
-export const unmuteResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseMuteResponse,
- ...punishmentEntryRemove
-} as const);
-
-export const kickResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse
-} as const);
-
-export const banResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...punishmentEntryAdd,
- ALREADY_BANNED: 'already banned'
-} as const);
-
-export const blockResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseBlockResponse,
- ...punishmentEntryAdd
-});
-
-export const unblockResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- ...baseBlockResponse,
- ...punishmentEntryRemove
-});
-
-export const timeoutResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse,
- INVALID_DURATION: 'duration too long'
-} as const);
-
-export const removeTimeoutResponse = Object.freeze({
- ...dmResponse,
- ...permissionsResponse
-} as const);
-
-/**
- * Response returned when warning a user.
- */
-export type WarnResponse = ValueOf<typeof warnResponse>;
-
-/**
- * Response returned when adding a role to a user.
- */
-export type AddRoleResponse = ValueOf<typeof addRoleResponse>;
-
-/**
- * Response returned when removing a role from a user.
- */
-export type RemoveRoleResponse = ValueOf<typeof removeRoleResponse>;
-
-/**
- * Response returned when muting a user.
- */
-export type MuteResponse = ValueOf<typeof muteResponse>;
-
-/**
- * Response returned when unmuting a user.
- */
-export type UnmuteResponse = ValueOf<typeof unmuteResponse>;
-
-/**
- * Response returned when kicking a user.
- */
-export type KickResponse = ValueOf<typeof kickResponse>;
-
-/**
- * Response returned when banning a user.
- */
-export type BanResponse = ValueOf<typeof banResponse>;
-
-/**
- * Response returned when blocking a user.
- */
-export type BlockResponse = ValueOf<typeof blockResponse>;
-
-/**
- * Response returned when unblocking a user.
- */
-export type UnblockResponse = ValueOf<typeof unblockResponse>;
-
-/**
- * Response returned when timing out a user.
- */
-export type TimeoutResponse = ValueOf<typeof timeoutResponse>;
-
-/**
- * Response returned when removing a timeout from a user.
- */
-export type RemoveTimeoutResponse = ValueOf<typeof removeTimeoutResponse>;
-
-/**
- * @typedef {BushClientEvents} VSCodePleaseDontRemove
- */
diff --git a/src/lib/extensions/discord.js/ExtendedMessage.ts b/src/lib/extensions/discord.js/ExtendedMessage.ts
deleted file mode 100644
index 4748803..0000000
--- a/src/lib/extensions/discord.js/ExtendedMessage.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { CommandUtil } from 'discord-akairo';
-import { Message, type Client } from 'discord.js';
-import { type RawMessageData } from 'discord.js/typings/rawDataTypes.js';
-
-export class ExtendedMessage<Cached extends boolean = boolean> extends Message<Cached> {
- public declare util: CommandUtil<Message>;
-
- public constructor(client: Client, data: RawMessageData) {
- super(client, data);
- this.util = new CommandUtil(client.commandHandler, this);
- }
-}
diff --git a/src/lib/extensions/discord.js/ExtendedUser.ts b/src/lib/extensions/discord.js/ExtendedUser.ts
deleted file mode 100644
index 23de523..0000000
--- a/src/lib/extensions/discord.js/ExtendedUser.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { User, type Partialize } from 'discord.js';
-
-declare module 'discord.js' {
- export interface User {
- /**
- * Indicates whether the user is an owner of the bot.
- */
- isOwner(): boolean;
- /**
- * Indicates whether the user is a superuser of the bot.
- */
- isSuperUser(): boolean;
- }
-}
-
-export type PartialBushUser = Partialize<ExtendedUser, 'username' | 'tag' | 'discriminator' | 'isOwner' | 'isSuperUser'>;
-
-/**
- * Represents a user on Discord.
- */
-export class ExtendedUser extends User {
- /**
- * Indicates whether the user is an owner of the bot.
- */
- public override isOwner(): boolean {
- return this.client.isOwner(this);
- }
-
- /**
- * Indicates whether the user is a superuser of the bot.
- */
- public override isSuperUser(): boolean {
- return this.client.isSuperUser(this);
- }
-}
diff --git a/src/lib/extensions/global.ts b/src/lib/extensions/global.ts
deleted file mode 100644
index a9020d7..0000000
--- a/src/lib/extensions/global.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/* eslint-disable no-var */
-declare global {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- interface ReadonlyArray<T> {
- includes<S, R extends `${Extract<S, string>}`>(
- this: ReadonlyArray<R>,
- searchElement: S,
- fromIndex?: number
- ): searchElement is R & S;
- }
-}
-
-export {};