From 661e4c9935aeb8760dafc7ced4bbec6cc356a033 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:47:57 -0400 Subject: remove the war crimes that I previously committed - Remove custom typings and replace with declaration merging - Fix the typings for args - Replace all discord-api-types imports with discord.js imports - Fix discord.js breaking changes --- .../discord-akairo/BushArgumentTypeCaster.ts | 4 +- src/lib/extensions/discord-akairo/BushClient.ts | 146 +-- .../extensions/discord-akairo/BushClientUtil.ts | 49 +- src/lib/extensions/discord-akairo/BushCommand.ts | 87 +- .../discord-akairo/BushCommandHandler.ts | 45 +- .../extensions/discord-akairo/BushCommandUtil.ts | 22 - src/lib/extensions/discord-akairo/BushInhibitor.ts | 9 +- .../discord-akairo/BushInhibitorHandler.ts | 5 +- src/lib/extensions/discord-akairo/BushListener.ts | 3 - .../discord-akairo/BushListenerHandler.ts | 5 +- .../extensions/discord-akairo/BushSlashMessage.ts | 34 - src/lib/extensions/discord-akairo/BushTask.ts | 11 +- .../extensions/discord-akairo/BushTaskHandler.ts | 9 +- src/lib/extensions/discord-akairo/SlashMessage.ts | 3 + src/lib/extensions/discord.js/BushActivity.ts | 14 - .../discord.js/BushApplicationCommand.ts | 16 - .../discord.js/BushApplicationCommandManager.ts | 151 --- .../BushApplicationCommandPermissionsManager.ts | 184 --- .../discord.js/BushBaseGuildEmojiManager.ts | 19 - .../discord.js/BushBaseGuildTextChannel.ts | 28 - .../discord.js/BushBaseGuildVoiceChannel.ts | 13 - .../extensions/discord.js/BushButtonInteraction.ts | 30 - .../extensions/discord.js/BushCategoryChannel.ts | 41 - .../discord.js/BushCategoryChannelChildManager.ts | 44 - src/lib/extensions/discord.js/BushChannel.ts | 39 - .../extensions/discord.js/BushChannelManager.ts | 25 - .../discord.js/BushChatInputCommandInteraction.ts | 49 - src/lib/extensions/discord.js/BushClientEvents.ts | 291 ++--- src/lib/extensions/discord.js/BushClientUser.ts | 98 -- src/lib/extensions/discord.js/BushDMChannel.ts | 45 - src/lib/extensions/discord.js/BushEmoji.ts | 14 - src/lib/extensions/discord.js/BushGuild.ts | 782 ------------ .../BushGuildApplicationCommandManager.ts | 114 -- src/lib/extensions/discord.js/BushGuildBan.ts | 15 - src/lib/extensions/discord.js/BushGuildChannel.ts | 47 - .../discord.js/BushGuildChannelManager.ts | 183 --- src/lib/extensions/discord.js/BushGuildEmoji.ts | 20 - .../discord.js/BushGuildEmojiRoleManager.ts | 55 - src/lib/extensions/discord.js/BushGuildManager.ts | 35 - src/lib/extensions/discord.js/BushGuildMember.ts | 1159 ------------------ .../discord.js/BushGuildMemberManager.ts | 177 --- src/lib/extensions/discord.js/BushMessage.ts | 63 - .../extensions/discord.js/BushMessageManager.ts | 113 -- .../extensions/discord.js/BushMessageReaction.ts | 20 - .../discord.js/BushModalSubmitInteraction.ts | 96 -- src/lib/extensions/discord.js/BushNewsChannel.ts | 15 - src/lib/extensions/discord.js/BushPresence.ts | 19 - src/lib/extensions/discord.js/BushReactionEmoji.ts | 16 - src/lib/extensions/discord.js/BushRole.ts | 18 - .../discord.js/BushSelectMenuInteraction.ts | 30 - src/lib/extensions/discord.js/BushStageChannel.ts | 20 - src/lib/extensions/discord.js/BushStageInstance.ts | 17 - src/lib/extensions/discord.js/BushTextChannel.ts | 42 - src/lib/extensions/discord.js/BushThreadChannel.ts | 47 - src/lib/extensions/discord.js/BushThreadManager.ts | 84 -- src/lib/extensions/discord.js/BushThreadMember.ts | 19 - .../discord.js/BushThreadMemberManager.ts | 62 - src/lib/extensions/discord.js/BushUser.ts | 34 - src/lib/extensions/discord.js/BushUserManager.ts | 60 - src/lib/extensions/discord.js/BushVoiceChannel.ts | 40 - src/lib/extensions/discord.js/BushVoiceState.ts | 20 - src/lib/extensions/discord.js/ExtendedGuild.ts | 870 ++++++++++++++ .../extensions/discord.js/ExtendedGuildMember.ts | 1240 ++++++++++++++++++++ src/lib/extensions/discord.js/ExtendedMessage.ts | 12 + src/lib/extensions/discord.js/ExtendedUser.ts | 35 + src/lib/extensions/discord.js/other.ts | 188 --- 66 files changed, 2380 insertions(+), 4920 deletions(-) delete mode 100644 src/lib/extensions/discord-akairo/BushCommandUtil.ts delete mode 100644 src/lib/extensions/discord-akairo/BushSlashMessage.ts create mode 100644 src/lib/extensions/discord-akairo/SlashMessage.ts delete mode 100644 src/lib/extensions/discord.js/BushActivity.ts delete mode 100644 src/lib/extensions/discord.js/BushApplicationCommand.ts delete mode 100644 src/lib/extensions/discord.js/BushApplicationCommandManager.ts delete mode 100644 src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.ts delete mode 100644 src/lib/extensions/discord.js/BushBaseGuildEmojiManager.ts delete mode 100644 src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushButtonInteraction.ts delete mode 100644 src/lib/extensions/discord.js/BushCategoryChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushCategoryChannelChildManager.ts delete mode 100644 src/lib/extensions/discord.js/BushChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushChannelManager.ts delete mode 100644 src/lib/extensions/discord.js/BushChatInputCommandInteraction.ts delete mode 100644 src/lib/extensions/discord.js/BushClientUser.ts delete mode 100644 src/lib/extensions/discord.js/BushDMChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushEmoji.ts delete mode 100644 src/lib/extensions/discord.js/BushGuild.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildApplicationCommandManager.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildBan.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildChannelManager.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildEmoji.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildEmojiRoleManager.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildManager.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildMember.ts delete mode 100644 src/lib/extensions/discord.js/BushGuildMemberManager.ts delete mode 100644 src/lib/extensions/discord.js/BushMessage.ts delete mode 100644 src/lib/extensions/discord.js/BushMessageManager.ts delete mode 100644 src/lib/extensions/discord.js/BushMessageReaction.ts delete mode 100644 src/lib/extensions/discord.js/BushModalSubmitInteraction.ts delete mode 100644 src/lib/extensions/discord.js/BushNewsChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushPresence.ts delete mode 100644 src/lib/extensions/discord.js/BushReactionEmoji.ts delete mode 100644 src/lib/extensions/discord.js/BushRole.ts delete mode 100644 src/lib/extensions/discord.js/BushSelectMenuInteraction.ts delete mode 100644 src/lib/extensions/discord.js/BushStageChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushStageInstance.ts delete mode 100644 src/lib/extensions/discord.js/BushTextChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushThreadChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushThreadManager.ts delete mode 100644 src/lib/extensions/discord.js/BushThreadMember.ts delete mode 100644 src/lib/extensions/discord.js/BushThreadMemberManager.ts delete mode 100644 src/lib/extensions/discord.js/BushUser.ts delete mode 100644 src/lib/extensions/discord.js/BushUserManager.ts delete mode 100644 src/lib/extensions/discord.js/BushVoiceChannel.ts delete mode 100644 src/lib/extensions/discord.js/BushVoiceState.ts create mode 100644 src/lib/extensions/discord.js/ExtendedGuild.ts create mode 100644 src/lib/extensions/discord.js/ExtendedGuildMember.ts create mode 100644 src/lib/extensions/discord.js/ExtendedMessage.ts create mode 100644 src/lib/extensions/discord.js/ExtendedUser.ts delete mode 100644 src/lib/extensions/discord.js/other.ts (limited to 'src/lib/extensions') diff --git a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts b/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts index 7a9a3db..def7ad6 100644 --- a/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts +++ b/src/lib/extensions/discord-akairo/BushArgumentTypeCaster.ts @@ -1,3 +1,3 @@ -import { type BushMessage } from '#lib'; +import { type CommandMessage } from '#lib'; -export type BushArgumentTypeCaster = (message: BushMessage, phrase: string) => R; +export type BushArgumentTypeCaster = (message: CommandMessage, phrase: string) => R; diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index db0ad91..2644231 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -10,28 +10,20 @@ import { roleWithDuration, snowflake } from '#args'; -import type { - BushBaseGuildEmojiManager, - BushChannelManager, - BushClientEvents, - BushClientUser, - BushGuildManager, - BushUserManager, - BushUserResolvable, - Config -} from '#lib'; +import type { BushClientEvents, Config } from '#lib'; import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; import * as Sentry from '@sentry/node'; import { AkairoClient, - ArgumentPromptData, ContextMenuCommandHandler, - OtherwiseContentSupplier, - version as akairoVersion + version as akairoVersion, + type ArgumentPromptData, + type ClientUtil, + type OtherwiseContentSupplier } from 'discord-akairo'; -import { GatewayIntentBits } from 'discord-api-types/v10'; import { ActivityType, + GatewayIntentBits, MessagePayload, Options, Partials, @@ -45,19 +37,21 @@ import { type MessageOptions, type ReplyMessageOptions, type Snowflake, + type UserResolvable, type WebhookEditMessageOptions } from 'discord.js'; -import EventEmitter from 'events'; +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 { tinyColor } from '../../../arguments/tinyColor.js'; import UpdateCacheTask from '../../../tasks/updateCache.js'; import UpdateStatsTask from '../../../tasks/updateStats.js'; import { HighlightManager } from '../../common/HighlightManager.js'; import { ActivePunishment } from '../../models/instance/ActivePunishment.js'; -import { Guild as GuildModel } from '../../models/instance/Guild.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'; @@ -71,26 +65,10 @@ import { AllowedMentions } from '../../utils/AllowedMentions.js'; import { BushCache } from '../../utils/BushCache.js'; import { BushConstants } from '../../utils/BushConstants.js'; import { BushLogger } from '../../utils/BushLogger.js'; -import { BushButtonInteraction } from '../discord.js/BushButtonInteraction.js'; -import { BushCategoryChannel } from '../discord.js/BushCategoryChannel.js'; -import { BushChatInputCommandInteraction } from '../discord.js/BushChatInputCommandInteraction.js'; -import { BushDMChannel } from '../discord.js/BushDMChannel.js'; -import { BushGuild } from '../discord.js/BushGuild.js'; -import { BushGuildEmoji } from '../discord.js/BushGuildEmoji.js'; -import { BushGuildMember } from '../discord.js/BushGuildMember.js'; -import { BushMessage } from '../discord.js/BushMessage.js'; -import { BushMessageReaction } from '../discord.js/BushMessageReaction.js'; -import { BushModalSubmitInteraction } from '../discord.js/BushModalSubmitInteraction.js'; -import { BushNewsChannel } from '../discord.js/BushNewsChannel.js'; -import { BushPresence } from '../discord.js/BushPresence.js'; -import { BushRole } from '../discord.js/BushRole.js'; -import { BushSelectMenuInteraction } from '../discord.js/BushSelectMenuInteraction.js'; -import { BushTextChannel } from '../discord.js/BushTextChannel.js'; -import { BushThreadChannel } from '../discord.js/BushThreadChannel.js'; -import { BushThreadMember } from '../discord.js/BushThreadMember.js'; -import { BushUser } from '../discord.js/BushUser.js'; -import { BushVoiceChannel } from '../discord.js/BushVoiceChannel.js'; -import { BushVoiceState } from '../discord.js/BushVoiceState.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 { BushClientUtil } from './BushClientUtil.js'; import { BushCommandHandler } from './BushCommandHandler.js'; import { BushInhibitorHandler } from './BushInhibitorHandler.js'; @@ -98,11 +76,43 @@ import { BushListenerHandler } from './BushListenerHandler.js'; import { BushTaskHandler } from './BushTaskHandler.js'; const { Sequelize } = (await import('sequelize')).default; -export type BushReplyMessageType = string | MessagePayload | ReplyMessageOptions; -export type BushEditMessageType = string | MessageEditOptions | MessagePayload; -export type BushSlashSendMessageType = string | MessagePayload | InteractionReplyOptions; -export type BushSlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions; -export type BushSendMessageType = string | MessagePayload | MessageOptions; +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[]; + /** + * Utility methods. + */ + util: ClientUtil | BushClientUtil; + on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; + once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; + emit(event: K, ...args: BushClientEvents[K]): boolean; + off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; + removeAllListeners(event?: K): this; + /** + * Checks if a user is the owner of this bot. + * @param user - User to check. + */ + isOwner(user: UserResolvable): boolean; + /** + * Checks if a user is a super user of this bot. + * @param user - User to check. + */ + isSuperUser(user: UserResolvable): boolean; + } +} + +export type ReplyMessageType = string | MessagePayload | ReplyMessageOptions; +export type EditMessageType = string | MessageEditOptions | MessagePayload; +export type SlashSendMessageType = string | MessagePayload | InteractionReplyOptions; +export type SlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions; +export type SendMessageType = string | MessagePayload | MessageOptions; const rl = readline.createInterface({ input: process.stdin, @@ -116,12 +126,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); * The main hub for interacting with the Discord API. */ export class BushClient extends AkairoClient { - public declare channels: BushChannelManager; - public declare guilds: BushGuildManager; - public declare user: If; - public declare users: BushUserManager; - public declare util: BushClientUtil; public declare ownerID: Snowflake[]; + public declare superUserID: Snowflake[]; + public declare util: BushClientUtil; /** * Whether or not the client is ready. @@ -272,7 +279,7 @@ export class BushClient extends AkairoClient { if (this.config.isDevelopment) return 'dev '; if (!guild) return this.config.prefix; - const prefix = await (guild as BushGuild).getSetting('prefix'); + const prefix = await guild.getSetting('prefix'); return (prefix ?? this.config.prefix) as string; }, allowMention: true, @@ -296,7 +303,6 @@ export class BushClient extends AkairoClient extends AkairoClient BushGuildEmoji); - Structures.extend('DMChannel', () => BushDMChannel); - Structures.extend('TextChannel', () => BushTextChannel); - Structures.extend('VoiceChannel', () => BushVoiceChannel); - Structures.extend('CategoryChannel', () => BushCategoryChannel); - Structures.extend('NewsChannel', () => BushNewsChannel); - Structures.extend('ThreadChannel', () => BushThreadChannel); - Structures.extend('GuildMember', () => BushGuildMember); - Structures.extend('ThreadMember', () => BushThreadMember); - Structures.extend('Guild', () => BushGuild); - Structures.extend('Message', () => BushMessage); - Structures.extend('MessageReaction', () => BushMessageReaction); - Structures.extend('Presence', () => BushPresence); - Structures.extend('VoiceState', () => BushVoiceState); - Structures.extend('Role', () => BushRole); - Structures.extend('User', () => BushUser); - Structures.extend('ChatInputCommandInteraction', () => BushChatInputCommandInteraction); - Structures.extend('ButtonInteraction', () => BushButtonInteraction); - Structures.extend('SelectMenuInteraction', () => BushSelectMenuInteraction); - Structures.extend('ModalSubmitInteraction', () => BushModalSubmitInteraction); + Structures.extend('GuildMember', () => ExtendedGuildMember); + Structures.extend('Guild', () => ExtendedGuild); + Structures.extend('Message', () => ExtendedMessage); + Structures.extend('User', () => ExtendedUser); } /** @@ -407,7 +397,8 @@ export class BushClient extends AkairoClient extends AkairoClient extends AkairoClient extends EventEmitter, PatchedElements, AkairoClient { - get emojis(): BushBaseGuildEmojiManager; - on(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - // on(event: Exclude, listener: (...args: any[]) => Awaitable): this; - once(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - // once(event: Exclude, listener: (...args: any[]) => Awaitable): this; - emit(event: K, ...args: BushClientEvents[K]): boolean; - // emit(event: Exclude, ...args: unknown[]): boolean; - off(event: K, listener: (...args: BushClientEvents[K]) => Awaitable): this; - // off(event: Exclude, listener: (...args: any[]) => Awaitable): this; - removeAllListeners(event?: K): this; - // removeAllListeners(event?: Exclude): this; } export interface BushStats { diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 9fe70fa..19810bd 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -1,41 +1,43 @@ import { Arg, - BaseBushArgumentType, BushConstants, + CommandMessage, Global, Shared, - SharedCache, + type BaseBushArgumentType, type BushClient, type BushInspectOptions, - type BushMessage, - type BushSlashEditMessageType, - type BushSlashMessage, - type BushSlashSendMessageType, - type BushUser, type CodeBlockLang, type GlobalCache, type Pronoun, - type PronounCode + type PronounCode, + type SharedCache, + type SlashEditMessageType, + type SlashMessage, + type SlashSendMessageType } from '#lib'; import { humanizeDuration } from '@notenoughupdates/humanize-duration'; import assert from 'assert'; import { exec } from 'child_process'; import deepLock from 'deep-lock'; import { ClientUtil, Util as AkairoUtil } from 'discord-akairo'; -import { APIEmbed, APIMessage, OAuth2Scopes, Routes } from 'discord-api-types/v10'; import { Constants as DiscordConstants, EmbedBuilder, GuildMember, Message, + OAuth2Scopes, PermissionFlagsBits, PermissionsBitField, - PermissionsString, + Routes, ThreadMember, User, Util as DiscordUtil, + type APIEmbed, + type APIMessage, type CommandInteraction, type InteractionReplyOptions, + type PermissionsString, type Snowflake, type TextChannel, type UserResolvable @@ -377,7 +379,7 @@ export class BushClientUtil extends ClientUtil { */ public async slashRespond( interaction: CommandInteraction, - responseOptions: BushSlashSendMessageType | BushSlashEditMessageType + responseOptions: SlashSendMessageType | SlashEditMessageType ): Promise { const newResponseOptions = typeof responseOptions === 'string' ? { content: responseOptions } : responseOptions; if (interaction.replied || interaction.deferred) { @@ -696,17 +698,17 @@ export class BushClientUtil extends ClientUtil { * @param user The user to fetch * @returns Undefined if the user is not found, otherwise the user. */ - public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { + public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise { if (user == null) return undefined; const resolvedUser = user instanceof User - ? user + ? user : user instanceof GuildMember - ? user.user + ? user.user : user instanceof ThreadMember - ? user.user + ? user.user : user instanceof Message - ? user.author + ? user.author : undefined; return resolvedUser ?? (await client.users.fetch(user as Snowflake).catch(() => undefined)); @@ -831,9 +833,10 @@ export class BushClientUtil extends ClientUtil { * @returns The missing permissions or null if none are missing. */ public userGuildPermCheck( - message: BushMessage | BushSlashMessage, + message: CommandMessage | SlashMessage, permissions: typeof PermissionFlagsBits[keyof typeof PermissionFlagsBits][] ): PermissionsString[] | null { + if (!message.inGuild()) return null; const missing = message.member?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; @@ -845,7 +848,7 @@ export class BushClientUtil extends ClientUtil { * @param permissions The permissions to check for. * @returns The missing permissions or null if none are missing. */ - public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: bigint[]): PermissionsString[] | null { + public clientGuildPermCheck(message: CommandMessage | SlashMessage, permissions: bigint[]): PermissionsString[] | null { const missing = message.guild?.members.me?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; @@ -860,7 +863,7 @@ export class BushClientUtil extends ClientUtil { * @returns The missing permissions or null if none are missing. */ public clientSendAndPermCheck( - message: BushMessage | BushSlashMessage, + message: CommandMessage | SlashMessage, permissions: bigint[] = [], checkChannel = false ): PermissionsString[] | null { @@ -868,7 +871,7 @@ export class BushClientUtil extends ClientUtil { const sendPerm = message.channel!.isThread() ? 'SendMessages' : 'SendMessagesInThreads'; if (!message.inGuild()) return null; - if (!message.guild.members.me!.permissionsIn(message.channel.id).has(sendPerm)) missing.push(sendPerm); + if (!message.guild.members.me!.permissionsIn(message.channel!.id).has(sendPerm)) missing.push(sendPerm); missing.push( ...(checkChannel @@ -884,7 +887,7 @@ export class BushClientUtil extends ClientUtil { * @param message The message to get the prefix from. * @returns The prefix. */ - public prefix(message: BushMessage | BushSlashMessage): string { + public prefix(message: CommandMessage | SlashMessage): string { return message.util.isSlash ? '/' : client.config.isDevelopment @@ -970,7 +973,7 @@ export class BushClientUtil extends ClientUtil { */ public async castDurationContent( arg: string | ParsedDuration | null, - message: BushMessage | BushSlashMessage + message: CommandMessage | SlashMessage ): Promise { const res = typeof arg === 'string' ? await util.arg.cast('contentWithDuration', message, arg) : arg; @@ -987,7 +990,7 @@ export class BushClientUtil extends ClientUtil { public async cast( type: T, arg: BaseBushArgumentType[T] | string, - message: BushMessage | BushSlashMessage + message: CommandMessage | SlashMessage ) { return typeof arg === 'string' ? await util.arg.cast(type, message, arg) : arg; } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index c727e98..5fb4e06 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -1,33 +1,17 @@ import { type DiscordEmojiInfo, type RoleWithDuration } from '#args'; import { type BushArgumentTypeCaster, - type BushBaseGuildVoiceChannel, - type BushCategoryChannel, type BushClient, type BushCommandHandler, - type BushEmoji, - type BushGuild, - type BushGuildBasedChannel, - type BushGuildChannel, - type BushGuildEmoji, - type BushGuildMember, type BushInhibitor, type BushListener, - type BushMessage, - type BushNewsChannel, - type BushRole, - type BushSlashMessage, - type BushStageChannel, type BushTask, - type BushTextChannel, - type BushThreadChannel, - type BushUser, - type BushVoiceChannel, type ParsedDuration } from '#lib'; import { ArgumentMatch, Command, + CommandUtil, type AkairoApplicationCommandAutocompleteOption, type AkairoApplicationCommandChannelOptionData, type AkairoApplicationCommandChoicesData, @@ -47,51 +31,17 @@ import { type SlashResolveType } from 'discord-akairo'; import { + Message, + User, type ApplicationCommandOptionChoiceData, - type Collection, - type Invite, type PermissionResolvable, type PermissionsString, type Snowflake } from 'discord.js'; import _ from 'lodash'; +import { SlashMessage } from './SlashMessage.js'; export interface OverriddenBaseArgumentType extends BaseArgumentType { - user: BushUser | null; - users: Collection | null; - member: BushGuildMember | null; - members: Collection | null; - relevant: BushUser | BushGuildMember | null; - relevants: Collection | Collection | null; - channel: BushGuildBasedChannel | BushBaseGuildVoiceChannel | null; - channels: Collection | null; - textChannel: BushTextChannel | null; - textChannels: Collection | null; - voiceChannel: BushVoiceChannel | null; - voiceChannels: Collection | null; - categoryChannel: BushCategoryChannel | null; - categoryChannels: Collection | null; - newsChannel: BushNewsChannel | null; - newsChannels: Collection | null; - stageChannel: BushStageChannel | null; - stageChannels: Collection | null; - threadChannel: BushThreadChannel | null; - threadChannels: Collection | null; - role: BushRole | null; - roles: Collection | null; - emoji: BushEmoji | null; - emojis: Collection | null; - guild: BushGuild | null; - guilds: Collection | null; - message: BushMessage | null; - guildMessage: BushMessage | null; - relevantMessage: BushMessage | null; - invite: Invite | null; - userMention: BushUser | null; - memberMention: BushGuildMember | null; - channelMention: BushThreadChannel | BushGuildChannel | null; - roleMention: BushRole | null; - emojiMention: BushGuildEmoji | null; commandAlias: BushCommand | null; command: BushCommand | null; inhibitor: BushInhibitor | null; @@ -108,9 +58,10 @@ export interface BaseBushArgumentType extends OverriddenBaseArgumentType { discordEmoji: DiscordEmojiInfo | null; roleWithDuration: RoleWithDuration | null; abbreviatedNumber: number | null; - globalUser: BushUser | null; - messageLink: BushMessage | null; + globalUser: User | null; + messageLink: Message | null; durationSeconds: number | null; + tinyColor: string | null; } export type BushArgumentType = keyof BaseBushArgumentType | RegExp; @@ -247,6 +198,7 @@ export interface BushArgumentOptions extends BaseBushArgumentOptions { */ 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. @@ -259,7 +211,7 @@ export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { customType?: (string | string[])[] | RegExp | string | null; } -export type BushMissingPermissionSupplier = (message: BushMessage | BushSlashMessage) => Promise | any; +export type BushMissingPermissionSupplier = (message: CommandMessage | SlashMessage) => Promise | any; interface ExtendedCommandOptions { /** @@ -407,9 +359,7 @@ export interface ArgsInfo { export class BushCommand extends Command { public declare client: BushClient; - public declare handler: BushCommandHandler; - public declare description: string; /** @@ -595,13 +545,13 @@ export interface BushCommand extends Command { * @param message - Message that triggered the command. * @param args - Evaluated arguments. */ - exec(message: BushMessage, args: any): any; + exec(message: CommandMessage, args: any): any; /** * Executes the command. * @param message - Message that triggered the command. * @param args - Evaluated arguments. */ - exec(message: BushMessage | BushSlashMessage, args: any): any; + exec(message: CommandMessage | SlashMessage, args: any): any; } type SlashOptionKeys = @@ -615,7 +565,22 @@ type SlashOptionKeys = interface PseudoArguments extends BaseBushArgumentType { boolean: boolean; + flag: boolean; + regex: { match: RegExpMatchArray; matches: RegExpExecArray[] }; } export type ArgType = NonNullable; export type OptArgType = PseudoArguments[T]; + +/** + * `util` is always defined for messages after `'all'` inhibitors + */ +export type CommandMessage = Message & { + /** + * Extra properties applied to the Discord.js message object. + * Utilities for command responding. + * Available on all messages after 'all' inhibitors and built-in inhibitors (bot, client). + * Not all properties of the util are available, depending on the input. + * */ + util: CommandUtil; +}; diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts index 2c1903f..f095356 100644 --- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts +++ b/src/lib/extensions/discord-akairo/BushCommandHandler.ts @@ -1,35 +1,30 @@ -import { type BushClient, type BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; +import { type BushClient, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo'; -import { type Collection, type PermissionsString } from 'discord.js'; +import { type Collection, type Message, type PermissionsString } from 'discord.js'; export type BushCommandHandlerOptions = CommandHandlerOptions; export interface BushCommandHandlerEvents extends CommandHandlerEvents { - commandBlocked: [message: BushMessage, command: BushCommand, reason: string]; - commandBreakout: [message: BushMessage, command: BushCommand, breakMessage: BushMessage]; - commandCancelled: [message: BushMessage, command: BushCommand, retryMessage?: BushMessage]; - commandFinished: [message: BushMessage, command: BushCommand, args: any, returnValue: any]; - commandInvalid: [message: BushMessage, command: BushCommand]; - commandLocked: [message: BushMessage, command: BushCommand]; - commandStarted: [message: BushMessage, command: BushCommand, args: any]; - cooldown: [message: BushMessage | BushSlashMessage, command: BushCommand, remaining: number]; - error: [error: Error, message: BushMessage, command?: BushCommand]; - inPrompt: [message: BushMessage]; + 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: BushMessage | BushSlashMessage, reason: string]; - messageInvalid: [message: BushMessage]; - missingPermissions: [message: BushMessage, command: BushCommand, type: 'client' | 'user', missing: PermissionsString[]]; + 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: BushSlashMessage, command: BushCommand, reason: string]; - slashError: [error: Error, message: BushSlashMessage, command: BushCommand]; - slashFinished: [message: BushSlashMessage, command: BushCommand, args: any, returnValue: any]; - slashMissingPermissions: [ - message: BushSlashMessage, - command: BushCommand, - type: 'client' | 'user', - missing: PermissionsString[] - ]; - slashStarted: [message: BushSlashMessage, command: BushCommand, args: any]; + 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 { diff --git a/src/lib/extensions/discord-akairo/BushCommandUtil.ts b/src/lib/extensions/discord-akairo/BushCommandUtil.ts deleted file mode 100644 index 7a06b35..0000000 --- a/src/lib/extensions/discord-akairo/BushCommandUtil.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { type BushCommand, type BushCommandHandler, type BushMessage, type BushSlashMessage } from '#lib'; -import { CommandUtil, type ParsedComponentData } from 'discord-akairo'; -import { type Collection, type Snowflake } from 'discord.js'; - -export interface BushParsedComponentData extends ParsedComponentData { - command?: BushCommand; -} - -export class BushCommandUtil extends CommandUtil { - public declare parsed: BushParsedComponentData | null; - public declare handler: BushCommandHandler; - public declare message: BushMessageType; - public declare messages: Collection | null; - - public constructor(handler: BushCommandHandler, message: BushMessageType) { - super(handler, message); - } -} - -export interface BushCommandUtil extends CommandUtil { - isSlashMessage(message: BushMessage | BushSlashMessage): message is BushSlashMessage; -} diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts index 7f13594..12b2baf 100644 --- a/src/lib/extensions/discord-akairo/BushInhibitor.ts +++ b/src/lib/extensions/discord-akairo/BushInhibitor.ts @@ -1,4 +1,4 @@ -import { type BushClient, type BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; +import { type BushClient, type BushCommand, type CommandMessage, type SlashMessage } from '#lib'; import { Inhibitor } from 'discord-akairo'; export class BushInhibitor extends Inhibitor { @@ -10,9 +10,12 @@ export interface BushInhibitor { * 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 `message.util` defined. + * * @param message - Message being handled. * @param command - Command to check. */ - exec(message: BushMessage, command: BushCommand): any; - exec(message: BushMessage | BushSlashMessage, command: BushCommand): any; + exec(message: CommandMessage, command: BushCommand): any; + 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 index a607bf7..5e4fb6c 100644 --- a/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts +++ b/src/lib/extensions/discord-akairo/BushInhibitorHandler.ts @@ -1,6 +1,3 @@ -import { type BushClient } from '#lib'; import { InhibitorHandler } from 'discord-akairo'; -export class BushInhibitorHandler extends InhibitorHandler { - public declare client: BushClient; -} +export class BushInhibitorHandler extends InhibitorHandler {} diff --git a/src/lib/extensions/discord-akairo/BushListener.ts b/src/lib/extensions/discord-akairo/BushListener.ts index f6247ec..3efe527 100644 --- a/src/lib/extensions/discord-akairo/BushListener.ts +++ b/src/lib/extensions/discord-akairo/BushListener.ts @@ -1,10 +1,7 @@ -import { type BushClient } from '#lib'; import { Listener } from 'discord-akairo'; import type EventEmitter from 'events'; export class BushListener extends Listener { - public declare client: BushClient; - public constructor( id: string, options: { diff --git a/src/lib/extensions/discord-akairo/BushListenerHandler.ts b/src/lib/extensions/discord-akairo/BushListenerHandler.ts index 517fb55..9c3e4af 100644 --- a/src/lib/extensions/discord-akairo/BushListenerHandler.ts +++ b/src/lib/extensions/discord-akairo/BushListenerHandler.ts @@ -1,6 +1,3 @@ -import { type BushClient } from '#lib'; import { ListenerHandler } from 'discord-akairo'; -export class BushListenerHandler extends ListenerHandler { - public declare client: BushClient; -} +export class BushListenerHandler extends ListenerHandler {} diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts deleted file mode 100644 index 0860964..0000000 --- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - BushCommandHandler, - BushGuildTextBasedChannel, - type BushClient, - type BushCommandUtil, - type BushGuild, - type BushGuildMember, - type BushTextBasedChannel, - type BushUser -} from '#lib'; -import { AkairoMessage } from 'discord-akairo'; -import { type ChatInputCommandInteraction } from 'discord.js'; - -export class BushSlashMessage extends AkairoMessage { - public declare client: BushClient; - public declare util: BushCommandUtil & { handler: BushCommandHandler }; - public declare author: BushUser; - public declare member: BushGuildMember | null; - public declare interaction: ChatInputCommandInteraction; - public constructor(client: BushClient, interaction: ChatInputCommandInteraction) { - super(client, interaction); - } -} - -export interface BushSlashMessage extends AkairoMessage { - get channel(): BushTextBasedChannel | null; - get guild(): BushGuild | null; - inGuild(): this is BushSlashMessageInGuild & this; -} - -interface BushSlashMessageInGuild { - guild: BushGuild; - channel: BushGuildTextBasedChannel; -} diff --git a/src/lib/extensions/discord-akairo/BushTask.ts b/src/lib/extensions/discord-akairo/BushTask.ts index b4359ce..9f5c0cd 100644 --- a/src/lib/extensions/discord-akairo/BushTask.ts +++ b/src/lib/extensions/discord-akairo/BushTask.ts @@ -1,10 +1,3 @@ -import { type BushClient } from '#lib'; -import { Task, type TaskOptions } from 'discord-akairo'; +import { Task } from 'discord-akairo'; -export class BushTask extends Task { - public declare client: BushClient; - - public constructor(id: string, options: TaskOptions) { - super(id, options); - } -} +export class BushTask extends Task {} diff --git a/src/lib/extensions/discord-akairo/BushTaskHandler.ts b/src/lib/extensions/discord-akairo/BushTaskHandler.ts index ed6b91d..f667ead 100644 --- a/src/lib/extensions/discord-akairo/BushTaskHandler.ts +++ b/src/lib/extensions/discord-akairo/BushTaskHandler.ts @@ -1,12 +1,5 @@ -import { type BushClient } from '#lib'; import { TaskHandler, type AkairoHandlerOptions } from 'discord-akairo'; export type BushTaskHandlerOptions = AkairoHandlerOptions; -export class BushTaskHandler extends TaskHandler { - public declare client: BushClient; - - public constructor(client: BushClient, options: BushTaskHandlerOptions) { - super(client, options); - } -} +export class BushTaskHandler extends TaskHandler {} diff --git a/src/lib/extensions/discord-akairo/SlashMessage.ts b/src/lib/extensions/discord-akairo/SlashMessage.ts new file mode 100644 index 0000000..0a6669b --- /dev/null +++ b/src/lib/extensions/discord-akairo/SlashMessage.ts @@ -0,0 +1,3 @@ +import { AkairoMessage } from 'discord-akairo'; + +export class SlashMessage extends AkairoMessage {} diff --git a/src/lib/extensions/discord.js/BushActivity.ts b/src/lib/extensions/discord.js/BushActivity.ts deleted file mode 100644 index 3f19232..0000000 --- a/src/lib/extensions/discord.js/BushActivity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { BushEmoji, BushPresence } from '#lib'; -import { Activity } from 'discord.js'; -import type { RawActivityData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents an activity that is part of a user's presence. - */ -export class BushActivity extends Activity { - public declare emoji: BushEmoji | null; - - public constructor(presence: BushPresence, data?: RawActivityData) { - super(presence, data); - } -} diff --git a/src/lib/extensions/discord.js/BushApplicationCommand.ts b/src/lib/extensions/discord.js/BushApplicationCommand.ts deleted file mode 100644 index 8298830..0000000 --- a/src/lib/extensions/discord.js/BushApplicationCommand.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import type { BushClient, BushGuild } from '#lib'; -import { ApplicationCommand, type Snowflake } from 'discord.js'; -import type { RawApplicationCommandData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents an application command. - */ -export class BushApplicationCommand extends ApplicationCommand { - public declare readonly client: BushClient; - public declare guild: BushGuild | null; - - public constructor(client: BushClient, data: RawApplicationCommandData, guild?: BushGuild, guildID?: Snowflake) { - super(client, data, guild, guildID); - } -} diff --git a/src/lib/extensions/discord.js/BushApplicationCommandManager.ts b/src/lib/extensions/discord.js/BushApplicationCommandManager.ts deleted file mode 100644 index dc27dbf..0000000 --- a/src/lib/extensions/discord.js/BushApplicationCommandManager.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { - BushApplicationCommand, - BushApplicationCommandPermissionsManager, - BushApplicationCommandResolvable, - BushClient, - BushGuildResolvable, - StripPrivate -} from '#lib'; -import type { APIApplicationCommand } from 'discord-api-types/v10'; -import { - ApplicationCommandManager, - CachedManager, - type ApplicationCommandData, - type Collection, - type FetchApplicationCommandOptions, - type Snowflake -} from 'discord.js'; - -/** - * Manages API methods for application commands and stores their cache. - */ -export declare class BushApplicationCommandManager< - ApplicationCommandScope = BushApplicationCommand<{ guild: BushGuildResolvable }>, - PermissionsOptionsExtras = { guild: BushGuildResolvable }, - PermissionsGuildType = null - > - extends CachedManager - implements StripPrivate> -{ - public constructor(client: BushClient, iterable?: Iterable); - - /** - * The manager for permissions of arbitrary commands on arbitrary guilds - */ - public permissions: BushApplicationCommandPermissionsManager< - { command?: BushApplicationCommandResolvable } & PermissionsOptionsExtras, - { command: BushApplicationCommandResolvable } & PermissionsOptionsExtras, - PermissionsOptionsExtras, - PermissionsGuildType, - null - >; - - /** - * The APIRouter path to the commands - * @param id The application command's id - * @param guildId The guild's id to use in the path, - * ignored when using a {@link GuildApplicationCommandManager} - */ - private commandPath({ id, guildId }: { id?: Snowflake; guildId?: Snowflake }): unknown; - - /** - * Creates an application command. - * @param command The command - * @param guildId The guild's id to create this command in, ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Create a new command - * client.application.commands.create({ - * name: 'test', - * description: 'A test command', - * }) - * .then(console.log) - * .catch(console.error); - */ - public create(command: BushApplicationCommandResolvable, guildId?: Snowflake): Promise; - - /** - * Deletes an application command. - * @param command The command to delete - * @param guildId The guild's id where the command is registered, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Delete a command - * guild.commands.delete('123456789012345678') - * .then(console.log) - * .catch(console.error); - */ - public delete(command: BushApplicationCommandResolvable, guildId?: Snowflake): Promise; - - /** - * Edits an application command. - * @param command The command to edit - * @param data The data to update the command with - * @param guildId The guild's id where the command registered, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Edit an existing command - * client.application.commands.edit('123456789012345678', { - * description: 'New description', - * }) - * .then(console.log) - * .catch(console.error); - */ - public edit(command: BushApplicationCommandResolvable, data: ApplicationCommandData): Promise; - public edit( - command: BushApplicationCommandResolvable, - data: ApplicationCommandData, - guildId: Snowflake - ): Promise; - - /** - * Obtains one or multiple application commands from Discord, or the cache if it's already available. - * @param id The application command's id - * @param options Additional options for this fetch - * @example - * // Fetch a single command - * client.application.commands.fetch('123456789012345678') - * .then(command => console.log(`Fetched command ${command.name}`)) - * .catch(console.error); - * @example - * // Fetch all commands - * guild.commands.fetch() - * .then(commands => console.log(`Fetched ${commands.size} commands`)) - * .catch(console.error); - */ - public fetch(id: Snowflake, options: FetchApplicationCommandOptions & { guildId: Snowflake }): Promise; - public fetch(options: FetchApplicationCommandOptions): Promise>; - public fetch(id: Snowflake, options?: FetchApplicationCommandOptions): Promise; - public fetch(id?: Snowflake, options?: FetchApplicationCommandOptions): Promise>; - - /** - * Sets all the commands for this application or guild. - * @param commands The commands - * @param guildId The guild's id to create the commands in, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Set all commands to just this one - * client.application.commands.set([ - * { - * name: 'test', - * description: 'A test command', - * }, - * ]) - * .then(console.log) - * .catch(console.error); - * @example - * // Remove all commands - * guild.commands.set([]) - * .then(console.log) - * .catch(console.error); - */ - public set(commands: ApplicationCommandData[]): Promise>; - public set(commands: ApplicationCommandData[], guildId: Snowflake): Promise>; - - /** - * Transforms an {@link ApplicationCommandData} object into something that can be used with the API. - * @param command The command to transform - */ - private static transformCommand( - command: ApplicationCommandData - ): Omit; -} diff --git a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.ts b/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.ts deleted file mode 100644 index 401f3e2..0000000 --- a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.ts +++ /dev/null @@ -1,184 +0,0 @@ -import type { BushClient, BushRoleResolvable, BushUserResolvable } from '#lib'; -import type { APIApplicationCommandPermission } from 'discord-api-types/v10'; -import { - ApplicationCommandPermissionType, - BaseManager, - type ApplicationCommand, - type ApplicationCommandManager, - type ApplicationCommandPermissionData, - type ApplicationCommandPermissions, - type Collection, - type GuildApplicationCommandManager, - type GuildApplicationCommandPermissionData, - type Snowflake -} from 'discord.js'; - -/** - * Manages API methods for permissions of Application Commands. - */ -export declare class BushApplicationCommandPermissionsManager< - BaseOptions, - FetchSingleOptions, - FullPermissionsOptions, - GuildType, - CommandIdType -> extends BaseManager { - public constructor(manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand); - - /** - * The manager or command that this manager belongs to - */ - private manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand; - - /** - * The client that instantiated this Manager - */ - public client: BushClient; - - /** - * The id of the command this manager acts on - */ - public commandId: CommandIdType; - - /** - * The guild that this manager acts on - */ - public guild: GuildType; - - /** - * The id of the guild that this manager acts on - */ - public guildId: Snowflake | null; - - /** - * Add permissions to a command. - * @param options Options used to add permissions - * @example - * // Block a role from the command permissions - * guild.commands.permissions.add({ command: '123456789012345678', permissions: [ - * { - * id: '876543211234567890', - * type: 'ROLE', - * permission: false - * }, - * ]}) - * .then(console.log) - * .catch(console.error); - */ - public add( - options: FetchSingleOptions & { permissions: ApplicationCommandPermissionData[] } - ): Promise; - - /** - * Check whether a permission exists for a user or role - * @param options Options used to check permissions - * @example - * // Check whether a user has permission to use a command - * guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' }) - * .then(console.log) - * .catch(console.error); - */ - public has(options: FetchSingleOptions & { permissionId: BushUserResolvable | BushRoleResolvable }): Promise; - - /** - * Fetches the permissions for one or multiple commands. - * @param options Options used to fetch permissions - * @example - * // Fetch permissions for one command - * guild.commands.permissions.fetch({ command: '123456789012345678' }) - * .then(perms => console.log(`Fetched permissions for ${perms.length} users`)) - * .catch(console.error); - * @example - * // Fetch permissions for all commands in a guild - * client.application.commands.permissions.fetch({ guild: '123456789012345678' }) - * .then(perms => console.log(`Fetched permissions for ${perms.size} commands`)) - * .catch(console.error); - */ - public fetch(options: FetchSingleOptions): Promise; - public fetch(options: BaseOptions): Promise>; - - /** - * Remove permissions from a command. - * @param options Options used to remove permissions - * @example - * // Remove a user permission from this command - * guild.commands.permissions.remove({ command: '123456789012345678', users: '876543210123456789' }) - * .then(console.log) - * .catch(console.error); - * @example - * // Remove multiple roles from this command - * guild.commands.permissions.remove({ - * command: '123456789012345678', roles: ['876543210123456789', '765432101234567890'] - * }) - * .then(console.log) - * .catch(console.error); - */ - public remove( - options: - | (FetchSingleOptions & { - users: BushUserResolvable | BushUserResolvable[]; - roles?: BushRoleResolvable | BushRoleResolvable[]; - }) - | (FetchSingleOptions & { - users?: BushUserResolvable | BushUserResolvable[]; - roles: BushRoleResolvable | BushRoleResolvable[]; - }) - ): Promise; - - /** - * Sets the permissions for one or more commands. - * @param options Options used to set permissions - * @example - * // Set the permissions for one command - * client.application.commands.permissions.set({ guild: '892455839386304532', command: '123456789012345678', - * permissions: [ - * { - * id: '876543210987654321', - * type: 'USER', - * permission: false, - * }, - * ]}) - * .then(console.log) - * .catch(console.error); - * @example - * // Set the permissions for all commands - * guild.commands.permissions.set({ fullPermissions: [ - * { - * id: '123456789012345678', - * permissions: [{ - * id: '876543210987654321', - * type: 'USER', - * permission: false, - * }], - * }, - * ]}) - * .then(console.log) - * .catch(console.error); - */ - public set( - options: FetchSingleOptions & { permissions: ApplicationCommandPermissionData[] } - ): Promise; - public set( - options: FullPermissionsOptions & { - fullPermissions: GuildApplicationCommandPermissionData[]; - } - ): Promise>; - - /** - * The APIRouter path to the commands - * @param guildId The guild's id to use in the path, - * @param commandId The application command's id - */ - private permissionsPath(guildId: Snowflake, commandId?: Snowflake): unknown; - - /** - * Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API. - * @param permissions The permissions to transform - * @param received Whether these permissions have been received from Discord - */ - private static transformPermissions( - permissions: ApplicationCommandPermissionData, - received: true - ): Omit & { type: keyof ApplicationCommandPermissionType }; - private static transformPermissions(permissions: ApplicationCommandPermissionData): APIApplicationCommandPermission; -} diff --git a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.ts b/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.ts deleted file mode 100644 index 66abbc2..0000000 --- a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { BushClient, BushEmojiIdentifierResolvable, BushEmojiResolvable, BushGuildEmoji } from '#lib'; -import { BaseGuildEmojiManager, CachedManager, type Snowflake } from 'discord.js'; -import { type RawGuildEmojiData } from 'discord.js/typings/rawDataTypes'; - -/** - * Holds methods to resolve GuildEmojis and stores their cache. - */ -export declare class BushBaseGuildEmojiManager - extends CachedManager - implements BaseGuildEmojiManager -{ - public constructor(client: BushClient, iterable?: Iterable); - - /** - * Resolves an EmojiResolvable to an emoji identifier. - * @param emoji The emoji resolvable to resolve - */ - public resolveIdentifier(emoji: BushEmojiIdentifierResolvable): string | null; -} diff --git a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts deleted file mode 100644 index 5cc74c4..0000000 --- a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { BushCategoryChannel, BushClient, BushGuild, BushGuildMember, BushMessageManager, BushThreadManager } from '#lib'; -import { - BaseGuildTextChannel, - type AllowedThreadTypeForNewsChannel, - type AllowedThreadTypeForTextChannel, - type Collection, - type Snowflake -} from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a text-based guild channel on Discord. - */ -export class BushBaseGuildTextChannel extends BaseGuildTextChannel { - public declare messages: BushMessageManager; - public declare threads: BushThreadManager; - public declare readonly client: BushClient; - public declare guild: BushGuild; - - public constructor(guild: BushGuild, data?: RawGuildChannelData, client?: BushClient, immediatePatch?: boolean) { - super(guild, data, client, immediatePatch); - } -} - -export interface BushBaseGuildTextChannel extends BaseGuildTextChannel { - get members(): Collection; - get parent(): BushCategoryChannel | null; -} diff --git a/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.ts b/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.ts deleted file mode 100644 index ba41cfe..0000000 --- a/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseGuildVoiceChannel, Collection, Snowflake } from 'discord.js'; -import { BushCategoryChannel } from './BushCategoryChannel.js'; -import { BushGuild } from './BushGuild.js'; -import { BushGuildMember } from './BushGuildMember.js'; - -/** - * Represents a voice-based guild channel on Discord. - */ -export declare class BushBaseGuildVoiceChannel extends BaseGuildVoiceChannel { - public get members(): Collection; - public guild: BushGuild; - public get parent(): BushCategoryChannel | null; -} diff --git a/src/lib/extensions/discord.js/BushButtonInteraction.ts b/src/lib/extensions/discord.js/BushButtonInteraction.ts deleted file mode 100644 index 368d19d..0000000 --- a/src/lib/extensions/discord.js/BushButtonInteraction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib'; -import type { APIInteractionGuildMember } from 'discord-api-types/v10'; -import { ButtonInteraction, type CacheType, type CacheTypeReducer } from 'discord.js'; -import type { RawMessageButtonInteractionData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a button interaction. - */ -export class BushButtonInteraction extends ButtonInteraction { - public declare member: CacheTypeReducer; - public declare user: BushUser; - - public constructor(client: BushClient, data: RawMessageButtonInteractionData) { - super(client, data); - } -} - -export interface BushButtonInteraction extends ButtonInteraction { - get channel(): CacheTypeReducer< - Cached, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushTextBasedChannel | null - >; - get guild(): CacheTypeReducer; - inGuild(): this is BushButtonInteraction<'raw' | 'cached'>; - inCachedGuild(): this is BushButtonInteraction<'cached'>; - inRawGuild(): this is BushButtonInteraction<'raw'>; -} diff --git a/src/lib/extensions/discord.js/BushCategoryChannel.ts b/src/lib/extensions/discord.js/BushCategoryChannel.ts deleted file mode 100644 index a2e1e1c..0000000 --- a/src/lib/extensions/discord.js/BushCategoryChannel.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - BushDMChannel, - BushGuildBasedChannel, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushVoiceBasedChannel, - BushVoiceChannel, - type BushCategoryChannelChildManager, - type BushClient, - type BushGuild -} from '#lib'; -import { CategoryChannel } from 'discord.js'; -import { type RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a guild category channel on Discord. - */ -export class BushCategoryChannel extends CategoryChannel { - public declare readonly client: BushClient; - public declare guild: BushGuild; - - public constructor(guild: BushGuild, data?: RawGuildChannelData, client?: BushClient, immediatePatch?: boolean) { - super(guild, data, client, immediatePatch); - } -} - -export interface BushCategoryChannel extends CategoryChannel { - get children(): BushCategoryChannelChildManager; - isText(): this is BushTextChannel; - isDM(): this is BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushGuildBasedChannel & BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushCategoryChannelChildManager.ts b/src/lib/extensions/discord.js/BushCategoryChannelChildManager.ts deleted file mode 100644 index 2b0d56b..0000000 --- a/src/lib/extensions/discord.js/BushCategoryChannelChildManager.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { - BushCategoryChannel, - BushGuild, - BushGuildChannelResolvable, - BushMappedChannelCategoryTypes, - BushNonCategoryGuildBasedChannel, - BushTextChannel -} from '#lib'; -import type { - CategoryChannelChildManager, - CategoryChannelType, - CategoryCreateChannelOptions, - DataManager, - Snowflake -} from 'discord.js'; - -export declare class BushCategoryChannelChildManager - extends DataManager - implements CategoryChannelChildManager -{ - private constructor(channel: BushCategoryChannel); - - /** - * The category channel this manager belongs to - */ - public channel: BushCategoryChannel; - - /** - * The guild this manager belongs to - */ - public readonly guild: BushGuild; - - /** - * Creates a new channel within this category. - * You cannot create a channel of type {@link ChannelType.GuildCategory} inside a CategoryChannel. - * @param name The name of the new channel - * @param options Options for creating the new channel - */ - public create( - name: string, - options: CategoryCreateChannelOptions & { type: T } - ): Promise; - public create(name: string, options?: CategoryCreateChannelOptions): Promise; -} diff --git a/src/lib/extensions/discord.js/BushChannel.ts b/src/lib/extensions/discord.js/BushChannel.ts deleted file mode 100644 index e66135c..0000000 --- a/src/lib/extensions/discord.js/BushChannel.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { - BushCategoryChannel, - BushClient, - BushDMChannel, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushVoiceBasedChannel, - BushVoiceChannel -} from '#lib'; -import { Channel, ChannelType, PartialGroupDMChannel, type Snowflake } from 'discord.js'; -import type { RawChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents any channel on Discord. - */ -export declare class BushChannel extends Channel { - public constructor(client: BushClient, data?: RawChannelData, immediatePatch?: boolean); - public get createdAt(): Date | null; - public get createdTimestamp(): number | null; - public deleted: boolean; - public id: Snowflake; - public get partial(): false; - public type: ChannelType; - public delete(): Promise; - public fetch(force?: boolean): Promise; - public isText(): this is BushTextChannel; - public isDM(): this is BushDMChannel; - public isDMBased(): this is PartialGroupDMChannel | BushDMChannel; - public isVoice(): this is BushVoiceChannel; - public isCategory(): this is BushCategoryChannel; - public isNews(): this is BushNewsChannel; - public isThread(): this is BushThreadChannel; - public isStage(): this is BushStageChannel; - public isTextBased(): this is BushTextBasedChannel; - public isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushChannelManager.ts b/src/lib/extensions/discord.js/BushChannelManager.ts deleted file mode 100644 index ff93209..0000000 --- a/src/lib/extensions/discord.js/BushChannelManager.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { BushAnyChannel, BushChannelResolvable } from '#lib'; -import { CachedManager, ChannelManager, type Client, type FetchChannelOptions, type Snowflake } from 'discord.js'; -import type { RawChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * A manager of channels belonging to a client - */ -export declare class BushChannelManager - extends CachedManager - implements ChannelManager -{ - public constructor(client: Client, iterable: Iterable); - - /** - * Obtains a channel from Discord, or the channel cache if it's already available. - * @param id The channel's id - * @param options Additional options for this fetch - * @example - * // Fetch a channel by its id - * client.channels.fetch('222109930545610754') - * .then(channel => console.log(channel.name)) - * .catch(console.error); - */ - public fetch(id: Snowflake, options?: FetchChannelOptions): Promise; -} diff --git a/src/lib/extensions/discord.js/BushChatInputCommandInteraction.ts b/src/lib/extensions/discord.js/BushChatInputCommandInteraction.ts deleted file mode 100644 index 2491a68..0000000 --- a/src/lib/extensions/discord.js/BushChatInputCommandInteraction.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - BushApplicationCommand, - BushClient, - BushGuild, - BushGuildEmoji, - BushGuildMember, - BushGuildTextBasedChannel, - BushNonThreadGuildBasedChannel, - BushRole, - BushTextBasedChannel, - BushUser -} from '#lib'; -import type { APIInteractionGuildMember } from 'discord-api-types/v10'; -import { ChatInputCommandInteraction, type CacheType, type CacheTypeReducer, type Invite, type Snowflake } from 'discord.js'; -import type { RawCommandInteractionData } from 'discord.js/typings/rawDataTypes'; - -export type BushGuildResolvable = - | BushGuild - | BushNonThreadGuildBasedChannel - | BushGuildMember - | BushGuildEmoji - | Invite - | BushRole - | Snowflake; - -/** - * Represents a command interaction. - */ -export class BushChatInputCommandInteraction extends ChatInputCommandInteraction { - public declare readonly client: BushClient; - public declare member: CacheTypeReducer; - public declare user: BushUser; - - public constructor(client: BushClient, data: RawCommandInteractionData) { - super(client, data); - } -} - -export interface BushChatInputCommandInteraction { - get command(): BushApplicationCommand | BushApplicationCommand<{ guild: BushGuildResolvable }> | null; - get channel(): CacheTypeReducer< - Cached, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushTextBasedChannel | null - >; - get guild(): CacheTypeReducer; -} diff --git a/src/lib/extensions/discord.js/BushClientEvents.ts b/src/lib/extensions/discord.js/BushClientEvents.ts index e1a9954..22bae65 100644 --- a/src/lib/extensions/discord.js/BushClientEvents.ts +++ b/src/lib/extensions/discord.js/BushClientEvents.ts @@ -1,176 +1,29 @@ import type { BanResponse, - BushApplicationCommand, - BushButtonInteraction, - BushClient, - BushDMChannel, - BushGuild, - BushGuildBan, - BushGuildEmoji, - BushGuildMember, - BushGuildTextBasedChannel, - BushMessage, - BushMessageReaction, - BushModalSubmitInteraction, - BushNewsChannel, - BushNonThreadGuildBasedChannel, - BushPresence, - BushRole, - BushSelectMenuInteraction, - BushStageInstance, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushThreadMember, - BushUser, - BushVoiceState, - Guild, - GuildSettings, - PartialBushGuildMember, - PartialBushMessage, - PartialBushMessageReaction, - PartialBushUser + CommandMessage, + Guild as GuildDB, + GuildSettings } from '#lib'; import type { AkairoClientEvents } from 'discord-akairo'; import type { + ButtonInteraction, Collection, - GuildScheduledEvent, - Interaction, - Invite, + Guild, + GuildMember, + GuildTextBasedChannel, + Message, + ModalSubmitInteraction, + Role, + SelectMenuInteraction, Snowflake, - Sticker, - Typing + User } from 'discord.js'; export interface BushClientEvents extends AkairoClientEvents { - applicationCommandCreate: [command: BushApplicationCommand]; - applicationCommandDelete: [command: BushApplicationCommand]; - applicationCommandUpdate: [ - oldCommand: BushApplicationCommand | null, - newCommand: BushApplicationCommand - ]; - channelCreate: [channel: BushNonThreadGuildBasedChannel]; - channelDelete: [channel: BushDMChannel | BushNonThreadGuildBasedChannel]; - channelPinsUpdate: [channel: BushTextBasedChannel, date: Date]; - channelUpdate: [ - oldChannel: BushDMChannel | BushNonThreadGuildBasedChannel, - newChannel: BushDMChannel | BushNonThreadGuildBasedChannel - ]; - debug: [message: string]; - warn: [message: string]; - emojiCreate: [emoji: BushGuildEmoji]; - emojiDelete: [emoji: BushGuildEmoji]; - emojiUpdate: [oldEmoji: BushGuildEmoji, newEmoji: BushGuildEmoji]; - error: [error: Error]; - guildBanAdd: [ban: BushGuildBan]; - guildBanRemove: [ban: BushGuildBan]; - guildCreate: [guild: BushGuild]; - guildDelete: [guild: BushGuild]; - guildUnavailable: [guild: BushGuild]; - guildIntegrationsUpdate: [guild: BushGuild]; - guildMemberAdd: [member: BushGuildMember]; - guildMemberAvailable: [member: BushGuildMember | PartialBushGuildMember]; - guildMemberRemove: [member: BushGuildMember | PartialBushGuildMember]; - guildMembersChunk: [ - members: Collection, - guild: BushGuild, - data: { - count: number; - index: number; - nonce: string | undefined; - } - ]; - guildMemberUpdate: [ - oldMember: BushGuildMember | PartialBushGuildMember, - newMember: BushGuildMember - ]; - guildUpdate: [oldGuild: BushGuild, newGuild: BushGuild]; - inviteCreate: [invite: Invite]; - inviteDelete: [invite: Invite]; - messageCreate: [message: BushMessage]; - messageDelete: [message: BushMessage | PartialBushMessage]; - messageReactionRemoveAll: [ - message: BushMessage | PartialBushMessage, - reactions: Collection - ]; - messageReactionRemoveEmoji: [ - reaction: BushMessageReaction | PartialBushMessageReaction - ]; - messageDeleteBulk: [ - messages: Collection, - channel: BushTextBasedChannel - ]; - messageReactionAdd: [ - reaction: BushMessageReaction | PartialBushMessageReaction, - user: BushUser | PartialBushUser - ]; - messageReactionRemove: [ - reaction: BushMessageReaction | PartialBushMessageReaction, - user: BushUser | PartialBushUser - ]; - messageUpdate: [ - oldMessage: BushMessage | PartialBushMessage, - newMessage: BushMessage | PartialBushMessage - ]; - presenceUpdate: [oldPresence: BushPresence | null, newPresence: BushPresence]; - ready: [client: BushClient]; - invalidated: []; - roleCreate: [role: BushRole]; - roleDelete: [role: BushRole]; - roleUpdate: [oldRole: BushRole, newRole: BushRole]; - threadCreate: [thread: BushThreadChannel, newlyCreated: boolean]; - threadDelete: [thread: BushThreadChannel]; - threadListSync: [ - threads: Collection, - guild: BushGuild - ]; - threadMemberUpdate: [ - oldMember: BushThreadMember, - newMember: BushThreadMember - ]; - threadMembersUpdate: [ - oldMembers: Collection, - newMembers: Collection, - thread: BushThreadChannel - ]; - threadUpdate: [oldThread: BushThreadChannel, newThread: BushThreadChannel]; - typingStart: [typing: Typing]; - userUpdate: [oldUser: BushUser | PartialBushUser, newUser: BushUser]; - voiceStateUpdate: [oldState: BushVoiceState, newState: BushVoiceState]; - webhookUpdate: [channel: BushTextChannel]; - interactionCreate: [interaction: Interaction]; - shardError: [error: Error, shardId: number]; - shardReady: [shardId: number, unavailableGuilds: Set | undefined]; - shardReconnecting: [shardId: number]; - shardResume: [shardId: number, replayedEvents: number]; - stageInstanceCreate: [stageInstance: BushStageInstance]; - stageInstanceUpdate: [ - oldStageInstance: BushStageInstance | null, - newStageInstance: BushStageInstance - ]; - stageInstanceDelete: [stageInstance: BushStageInstance]; - stickerCreate: [sticker: Sticker]; - stickerDelete: [sticker: Sticker]; - stickerUpdate: [oldSticker: Sticker, newSticker: Sticker]; - guildScheduledEventCreate: [guildScheduledEvent: GuildScheduledEvent]; - guildScheduledEventUpdate: [ - oldGuildScheduledEvent: GuildScheduledEvent, - newGuildScheduledEvent: GuildScheduledEvent - ]; - guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent]; - guildScheduledEventUserAdd: [ - guildScheduledEvent: GuildScheduledEvent, - user: BushUser - ]; - guildScheduledEventUserRemove: [ - guildScheduledEvent: GuildScheduledEvent, - user: BushUser - ]; - /* Custom */ bushBan: [ - victim: BushGuildMember | BushUser, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember | User, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, duration: number, @@ -178,29 +31,29 @@ export interface BushClientEvents extends AkairoClientEvents { evidence?: string ]; bushBlock: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, duration: number, dmSuccess: boolean, - channel: BushGuildTextBasedChannel, + channel: GuildTextBasedChannel, evidence?: string ]; bushKick: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, evidence?: string ]; bushMute: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, duration: number, @@ -208,43 +61,43 @@ export interface BushClientEvents extends AkairoClientEvents { evidence?: string ]; bushPunishRole: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, duration: number, - role: BushRole, + role: Role, evidence?: string ]; bushPunishRoleRemove: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, - role: BushRole, + role: Role, evidence?: string ]; bushPurge: [ - moderator: BushUser, - guild: BushGuild, - channel: BushTextChannel | BushNewsChannel | BushThreadChannel, - messages: Collection + moderator: User, + guild: Guild, + channel: GuildTextBasedChannel, + messages: Collection ]; bushRemoveTimeout: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, evidence?: string ]; bushTimeout: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, duration: number, @@ -252,35 +105,35 @@ export interface BushClientEvents extends AkairoClientEvents { evidence?: string ]; bushUnban: [ - victim: BushUser, - moderator: BushUser, - guild: BushGuild, + victim: User, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, evidence?: string ]; bushUnblock: [ - victim: BushGuildMember | BushUser, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember | User, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, - channel: BushGuildTextBasedChannel, + channel: GuildTextBasedChannel, evidence?: string ]; bushUnmute: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, evidence?: string ]; bushUpdateModlog: [ - moderator: BushGuildMember, + moderator: GuildMember, modlogID: string, key: 'evidence' | 'hidden', oldModlog: string | boolean, @@ -288,55 +141,55 @@ export interface BushClientEvents extends AkairoClientEvents { ]; bushUpdateSettings: [ setting: Setting, - guild: BushGuild, - oldValue: Guild[Setting], - newValue: Guild[Setting], - moderator?: BushGuildMember + guild: Guild, + oldValue: GuildDB[Setting], + newValue: GuildDB[Setting], + moderator?: GuildMember ]; bushWarn: [ - victim: BushGuildMember, - moderator: BushUser, - guild: BushGuild, + victim: GuildMember, + moderator: User, + guild: Guild, reason: string | undefined, caseID: string, dmSuccess: boolean, evidence?: string ]; bushLevelUpdate: [ - member: BushGuildMember, + member: GuildMember, oldLevel: number, newLevel: number, currentXp: number, - message: BushMessage & { guild: BushGuild } + message: CommandMessage ]; bushLockdown: [ - moderator: BushGuildMember, + moderator: GuildMember, reason: string | undefined, channelsSuccessMap: Collection, all?: boolean ]; bushUnlockdown: [ - moderator: BushGuildMember, + moderator: GuildMember, reason: string | undefined, channelsSuccessMap: Collection, all?: boolean ]; massBan: [ - moderator: BushGuildMember, - guild: BushGuild, + moderator: GuildMember, + guild: Guild, reason: string | undefined, results: Collection ]; massEvidence: [ - moderator: BushGuildMember, - guild: BushGuild, + moderator: GuildMember, + guild: Guild, evidence: string, lines: string[] ]; /* components */ - button: [button: BushButtonInteraction]; - selectMenu: [selectMenu: BushSelectMenuInteraction]; - modal: [modal: BushModalSubmitInteraction]; + button: [button: ButtonInteraction]; + selectMenu: [selectMenu: SelectMenuInteraction]; + modal: [modal: ModalSubmitInteraction]; } type Setting = diff --git a/src/lib/extensions/discord.js/BushClientUser.ts b/src/lib/extensions/discord.js/BushClientUser.ts deleted file mode 100644 index cee9808..0000000 --- a/src/lib/extensions/discord.js/BushClientUser.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { - ActivityOptions, - Base64Resolvable, - BufferResolvable, - ClientPresence, - ClientUser, - ClientUserEditData, - PresenceData, - PresenceStatusData -} from 'discord.js'; -import { BushUser } from './BushUser.js'; - -/** - * Represents the logged in client's Discord user. - */ -export declare class BushClientUser extends BushUser implements ClientUser { - /** - * If the bot's {@link ClientApplication.owner Owner} has MFA enabled on their account - */ - public mfaEnabled: boolean; - - /** - * Represents the client user's presence - */ - public readonly presence: ClientPresence; - - /** - * Whether or not this account has been verified - */ - public verified: boolean; - - /** - * Edits the logged in client. - * @param data The new data - */ - public edit(data: ClientUserEditData): Promise; - - /** - * Sets the activity the client user is playing. - * @param name Activity being played, or options for setting the activity - * @param options Options for setting the activity - * @example - * // Set the client user's activity - * client.user.setActivity('discord.js', { type: 'WATCHING' }); - */ - public setActivity(options?: ActivityOptions): ClientPresence; - public setActivity(name: string, options?: ActivityOptions): ClientPresence; - - /** - * Sets/removes the AFK flag for the client user. - * @param afk Whether or not the user is AFK - * @param shardId Shard Id(s) to have the AFK flag set on - */ - public setAFK(afk: boolean, shardId?: number | number[]): ClientPresence; - - /** - * Sets the avatar of the logged in client. - * @param avatar The new avatar - * @example - * // Set avatar - * client.user.setAvatar('./avatar.png') - * .then(user => console.log(`New avatar set!`)) - * .catch(console.error); - */ - public setAvatar(avatar: BufferResolvable | Base64Resolvable | null): Promise; - - /** - * Sets the full presence of the client user. - * @param data Data for the presence - * @example - * // Set the client user's presence - * client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' }); - */ - public setPresence(data: PresenceData): ClientPresence; - - /** - * Sets the status of the client user. - * @param status Status to change to - * @param shardId Shard id(s) to have the activity set on - * @example - * // Set the client user's status - * client.user.setStatus('idle'); - */ - public setStatus(status: PresenceStatusData, shardId?: number | number[]): ClientPresence; - - /** - * Sets the username of the logged in client. - * Changing usernames in Discord is heavily rate limited, with only 2 requests - * every hour. Use this sparingly! - * @param username The new username - * @example - * // Set username - * client.user.setUsername('discordjs') - * .then(user => console.log(`My new username is ${user.username}`)) - * .catch(console.error); - */ - public setUsername(username: string): Promise; -} diff --git a/src/lib/extensions/discord.js/BushDMChannel.ts b/src/lib/extensions/discord.js/BushDMChannel.ts deleted file mode 100644 index 87382ec..0000000 --- a/src/lib/extensions/discord.js/BushDMChannel.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { - BushCategoryChannel, - BushClient, - BushMessageManager, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushUser, - BushVoiceBasedChannel, - BushVoiceChannel -} from '#lib'; -import { DMChannel, PartialGroupDMChannel, type Partialize } from 'discord.js'; -import type { RawDMChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a direct message channel between two users. - */ -export class BushDMChannel extends DMChannel { - public declare readonly client: BushClient; - public declare messages: BushMessageManager; - - public constructor(client: BushClient, data?: RawDMChannelData) { - super(client, data); - } -} - -export interface BushDMChannel extends DMChannel { - get recipient(): BushUser | null; - isText(): this is BushTextChannel; - isDM(): this is BushDMChannel; - isDMBased(): this is PartialGroupDMChannel | BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} - -export interface PartialBushDMChannel extends Partialize { - lastMessageId: undefined; -} diff --git a/src/lib/extensions/discord.js/BushEmoji.ts b/src/lib/extensions/discord.js/BushEmoji.ts deleted file mode 100644 index 9e42e5d..0000000 --- a/src/lib/extensions/discord.js/BushEmoji.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { BushClient } from '#lib'; -import { Emoji } from 'discord.js'; -import type { RawEmojiData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}. - */ -export class BushEmoji extends Emoji { - public declare readonly client: BushClient; - - public constructor(client: BushClient, emoji: RawEmojiData) { - super(client, emoji); - } -} diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts deleted file mode 100644 index a93e35f..0000000 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ /dev/null @@ -1,782 +0,0 @@ -import { - AllowedMentions, - banResponse, - BushGuildChannelManager, - BushGuildMemberManager, - BushMessage, - BushVoiceChannel, - dmResponse, - permissionsResponse, - punishmentEntryRemove, - type BanResponse, - type BushClient, - type BushGuildMember, - type BushGuildMemberResolvable, - type BushNewsChannel, - type BushTextChannel, - type BushThreadChannel, - type BushUser, - type BushUserResolvable, - type GuildFeatures, - type GuildLogType, - type GuildModel -} from '#lib'; -import { APIMessage } from 'discord-api-types/v10'; -import { - Collection, - Guild, - MessageType, - PermissionFlagsBits, - SnowflakeUtil, - ThreadChannel, - Webhook, - WebhookMessageOptions, - type MessageOptions, - type MessagePayload, - type Snowflake -} from 'discord.js'; -import type { RawGuildData } from 'discord.js/typings/rawDataTypes'; -import _ from 'lodash'; -import { Moderation } from '../../common/util/Moderation.js'; -import { Guild as GuildDB } from '../../models/instance/Guild.js'; -import { ModLogType } from '../../models/instance/ModLog.js'; - -/** - * Represents a guild (or a server) on Discord. - * It's recommended to see if a guild is available before performing operations or reading data from it. You can - * check this with {@link BushGuild.available}. - */ -export class BushGuild extends Guild { - public declare readonly client: BushClient; - public declare members: BushGuildMemberManager; - public declare channels: BushGuildChannelManager; - - public constructor(client: BushClient, data: RawGuildData) { - super(client, data); - } - - /** - * Checks if the guild has a certain custom feature. - * @param feature The feature to check for - */ - public async hasFeature(feature: GuildFeatures): Promise { - const features = await this.getSetting('enabledFeatures'); - return features.includes(feature); - } - - /** - * Adds a custom feature to the guild. - * @param feature The feature to add - * @param moderator The moderator responsible for adding a feature - */ - public async addFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise { - const features = await this.getSetting('enabledFeatures'); - const newFeatures = util.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 async removeFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise { - const features = await this.getSetting('enabledFeatures'); - const newFeatures = util.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 async toggleFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise { - return (await this.hasFeature(feature)) - ? await this.removeFeature(feature, moderator) - : await this.addFeature(feature, moderator); - } - - /** - * Fetches a custom setting for the guild - * @param setting The setting to get - */ - public async getSetting(setting: K): Promise { - return ( - 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 async setSetting>( - setting: K, - value: GuildDB[K], - moderator?: BushGuildMember - ): Promise { - const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }); - const oldValue = row[setting] as GuildDB[K]; - row[setting] = value; - client.cache.guilds.set(this.id, row.toJSON() as GuildDB); - 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 async getLogChannel(logType: GuildLogType): Promise { - const channelId = (await this.getSetting('logChannels'))[logType]; - if (!channelId) return undefined; - return ( - (this.channels.cache.get(channelId) as BushTextChannel | undefined) ?? - ((await this.channels.fetch(channelId)) as BushTextChannel | 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 async sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions) { - 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 async error(title: string, message: string) { - void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); - void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.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 async bushBan(options: GuildBushBanOptions): Promise { - // checks - if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; - - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; - const user = await util.resolveNonCachedUser(options.user); - const moderator = client.users.resolve(options.moderator ?? 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({ - 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({ - 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({ - 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)) - 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 async massBanOne(options: GuildMassBanOneOptions): Promise { - if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED; - - const ret = await (async () => { - // add modlog entry - const { log: modlog } = await Moderation.createModLogEntrySimple({ - 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({ - 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({ - 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 async bushUnban(options: GuildBushUnbanOptions): Promise { - // 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 util.resolveNonCachedUser(options.user); - const moderator = client.users.resolve(options.moderator ?? 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({ - 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({ - type: 'ban', - user: user.id, - guild: this - }); - if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; - - // dm user - dmSuccessEvent = await Moderation.punishDM({ - 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 - ) - ) - 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 async lockdown(options: LockdownOptions): Promise { - 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(); - const success = new Collection(); - const ret = await (async (): Promise => { - 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 ? util.colors.Green : util.colors.Red, - timestamp: new Date().toISOString() - } - ] - }); - } - } - - if (errors.size) return errors; - else return `success: ${success.filter((c) => c === true).size}`; - })(); - - client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all); - return ret; - } - - public async quote(rawQuote: APIMessage, channel: BushTextChannel | BushNewsChannel | BushThreadChannel) { - 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 BushMessage(client, rawQuote); - - const target = channel instanceof ThreadChannel ? channel.parent : channel; - if (!target) return null; - - const webhooks: Collection = 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(`${client.user!.username} Quotes #${target.name}`, { - avatar: client.user!.displayAvatarURL({ size: 2048 }), - reason: 'Creating a webhook for quoting' - }) - .catch(() => null); - - if (!webhook) return null; - - const sendOptions: Omit = {}; - - 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; - sendOptions.attachments = quote.attachments.size ? [...quote.attachments.values()] : 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 = `${util.emojis.error} Cannot resolve recipient.`; - break; - } - - if (quote.channel.isThread()) { - const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username; - sendOptions.content = `${util.emojis.join} ${displayName} added ${recipientDisplay} to the thread.`; - } else { - // this should never happen - sendOptions.content = `${util.emojis.join} ${displayName} added ${recipient.username} to the group.`; - } - - break; - } - case MessageType.RecipientRemove: { - const recipient = rawQuote.mentions[0]; - if (!recipient) { - sendOptions.content = `${util.emojis.error} Cannot resolve recipient.`; - break; - } - - if (quote.channel.isThread()) { - const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username; - sendOptions.content = `${util.emojis.leave} ${displayName} removed ${recipientDisplay} from the thread.`; - } else { - // this should never happen - sendOptions.content = `${util.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.GuildMemberJoin: { - 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 = `${util.emojis.join} ${message}`; - break; - } - case MessageType.UserPremiumGuildSubscription: - sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${ - quote.content ? ` **${quote.content}** times` : '' - }!`; - - break; - case MessageType.UserPremiumGuildSubscriptionTier1: - case MessageType.UserPremiumGuildSubscriptionTier2: - case MessageType.UserPremiumGuildSubscriptionTier3: - 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; - case MessageType.ChannelIconChange: - case MessageType.Call: - default: - sendOptions.content = `${util.emojis.error} I cannot quote **${ - 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: BushUserResolvable | BushUser; - - /** - * The reason for unbanning the user - */ - reason?: string | null; - - /** - * The moderator who unbanned the user - */ - moderator?: BushUserResolvable; - - /** - * 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: BushUserResolvable; - - /** - * The reason to ban the user - */ - reason?: string | null; - - /** - * The moderator who banned the user - */ - moderator?: BushUserResolvable; - - /** - * 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[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; - -/** - * Options for locking down channel(s) - */ -export interface LockdownOptions { - /** - * The moderator responsible for the lockdown - */ - moderator: BushGuildMemberResolvable; - - /** - * Whether to lock down all (specified) channels - */ - all: boolean; - - /** - * Reason for the lockdown - */ - reason?: string; - - /** - * A specific channel to lockdown - */ - channel?: BushThreadChannel | BushNewsChannel | BushTextChannel | BushVoiceChannel; - - /** - * 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; diff --git a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.ts b/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.ts deleted file mode 100644 index ba9db66..0000000 --- a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import { - BushApplicationCommandManager, - type BushApplicationCommand, - type BushApplicationCommandResolvable, - type BushClient, - type BushGuild -} from '#lib'; -import type { ApplicationCommandData, BaseFetchOptions, Collection, Snowflake } from 'discord.js'; -import type { RawApplicationCommandData } from 'discord.js/typings/rawDataTypes'; - -/** - * An extension for guild-specific application commands. - */ -export declare class BushGuildApplicationCommandManager extends BushApplicationCommandManager< - BushApplicationCommand, - {}, - BushGuild -> { - public constructor(guild: BushGuild, iterable?: Iterable); - public declare readonly client: BushClient; - - /** - * The guild that this manager belongs to - */ - public guild: BushGuild; - - /** - * Creates an application command. - * @param command The command - * @param guildId The guild's id to create this command in, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Create a new command - * client.application.commands.create({ - * name: 'test', - * description: 'A test command', - * }) - * .then(console.log) - * .catch(console.error); - */ - public create(command: BushApplicationCommandResolvable): Promise; - - /** - * Deletes an application command. - * @param command The command to delete - * @param guildId The guild's id where the command is registered, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Delete a command - * guild.commands.delete('123456789012345678') - * .then(console.log) - * .catch(console.error); - */ - public delete(command: BushApplicationCommandResolvable): Promise; - - /** - * Edits an application command. - * @param command The command to edit - * @param data The data to update the command with - * @param guildId The guild's id where the command registered, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Edit an existing command - * client.application.commands.edit('123456789012345678', { - * description: 'New description', - * }) - * .then(console.log) - * .catch(console.error); - */ - public edit(command: BushApplicationCommandResolvable, data: ApplicationCommandData): Promise; - - /** - * Obtains one or multiple application commands from Discord, or the cache if it's already available. - * @param id The application command's id - * @param options Additional options for this fetch - * @example - * // Fetch a single command - * client.application.commands.fetch('123456789012345678') - * .then(command => console.log(`Fetched command ${command.name}`)) - * .catch(console.error); - * @example - * // Fetch all commands - * guild.commands.fetch() - * .then(commands => console.log(`Fetched ${commands.size} commands`)) - * .catch(console.error); - */ - public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; - public fetch(options: BaseFetchOptions): Promise>; - public fetch(id?: undefined, options?: BaseFetchOptions): Promise>; - - /** - * Sets all the commands for this application or guild. - * @param commands The commands - * @param guildId The guild's id to create the commands in, - * ignored when using a {@link GuildApplicationCommandManager} - * @example - * // Set all commands to just this one - * client.application.commands.set([ - * { - * name: 'test', - * description: 'A test command', - * }, - * ]) - * .then(console.log) - * .catch(console.error); - * @example - * // Remove all commands - * guild.commands.set([]) - * .then(console.log) - * .catch(console.error); - */ - public set(commands: ApplicationCommandData[]): Promise>; -} diff --git a/src/lib/extensions/discord.js/BushGuildBan.ts b/src/lib/extensions/discord.js/BushGuildBan.ts deleted file mode 100644 index d56c531..0000000 --- a/src/lib/extensions/discord.js/BushGuildBan.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { BushClient, BushGuild, BushUser } from '#lib'; -import { GuildBan } from 'discord.js'; -import type { RawGuildBanData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a ban in a guild on Discord. - */ -export declare class BushGuildBan extends GuildBan { - public constructor(client: BushClient, data: RawGuildBanData, guild: BushGuild); - public guild: BushGuild; - public user: BushUser; - public get partial(): boolean; - public reason?: string | null; - public fetch(force?: boolean): Promise; -} diff --git a/src/lib/extensions/discord.js/BushGuildChannel.ts b/src/lib/extensions/discord.js/BushGuildChannel.ts deleted file mode 100644 index 62bf05a..0000000 --- a/src/lib/extensions/discord.js/BushGuildChannel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - BushCategoryChannel, - BushClient, - BushDMChannel, - BushGuild, - BushGuildBasedChannel, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushVoiceBasedChannel, - BushVoiceChannel -} from '#lib'; -import { GuildChannel, PartialGroupDMChannel } from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a guild channel from any of the following: - * - {@link BushTextChannel} - * - {@link BushVoiceChannel} - * - {@link BushCategoryChannel} - * - {@link BushNewsChannel} - * - {@link BushStoreChannel} - * - {@link BushStageChannel} - */ -export class BushGuildChannel extends GuildChannel { - public declare readonly client: BushClient; - public declare guild: BushGuild; - - public constructor(guild: BushGuild, data?: RawGuildChannelData, client?: BushClient, immediatePatch?: boolean) { - super(guild, data, client, immediatePatch); - } -} - -export interface BushGuildChannel extends GuildChannel { - isText(): this is BushTextChannel; - isDMBased(): this is PartialGroupDMChannel | BushDMChannel; - isDM(): this is BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushGuildBasedChannel & BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushGuildChannelManager.ts b/src/lib/extensions/discord.js/BushGuildChannelManager.ts deleted file mode 100644 index 4048b98..0000000 --- a/src/lib/extensions/discord.js/BushGuildChannelManager.ts +++ /dev/null @@ -1,183 +0,0 @@ -import type { - BushFetchedThreads, - BushGuild, - BushGuildBasedChannel, - BushGuildChannel, - BushMappedGuildChannelTypes, - BushNonThreadGuildBasedChannel, - BushTextChannel -} from '#lib'; -import { - CachedManager, - ChannelData, - ChannelWebhookCreateOptions, - SetChannelPositionOptions, - Webhook, - type BaseFetchOptions, - type ChannelPosition, - type Collection, - type GuildChannelCreateOptions, - type GuildChannelManager, - type GuildChannelResolvable, - type GuildChannelTypes, - type Snowflake -} from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for GuildChannels and stores their cache. - */ -export declare class BushGuildChannelManager - extends CachedManager - implements GuildChannelManager -{ - public constructor(guild: BushGuild, iterable?: Iterable); - - /** - * The number of channels in this managers cache excluding thread channels - * that do not count towards a guild's maximum channels restriction. - */ - public readonly channelCountWithoutThreads: number; - - /** - * The guild this Manager belongs to - */ - public guild: BushGuild; - - /** - * Creates a new channel in the guild. - * @param name The name of the new channel - * @param options Options for creating the new channel - * @example - * // Create a new text channel - * guild.channels.create('new-general', { reason: 'Needed a cool new channel' }) - * .then(console.log) - * .catch(console.error); - * @example - * // Create a new channel with permission overwrites - * guild.channels.create('new-voice', { - * type: 'GuildVoice', - * permissionOverwrites: [ - * { - * id: message.author.id, - * deny: [PermissionFlagsBits.ViewChannel], - * }, - * ], - * }) - */ - public create( - name: string, - options: GuildChannelCreateOptions & { type: T } - ): Promise; - public create(name: string, options?: GuildChannelCreateOptions): Promise; - - /** - * Creates a webhook for the channel. - * @param channel The channel to create the webhook for - * @param name The name of the webhook - * @param options Options for creating the webhook - * @returns Returns the created Webhook - * @example - * // Create a webhook for the current channel - * guild.channels.createWebhook('222197033908436994', 'Snek', { - * avatar: 'https://i.imgur.com/mI8XcpG.jpg', - * reason: 'Needed a cool new Webhook' - * }) - * .then(console.log) - * .catch(console.error) - */ - public createWebhook(channel: GuildChannelResolvable, name: string, options?: ChannelWebhookCreateOptions): Promise; - - /** - * Edits the channel. - * @param channel The channel to edit - * @param data The new data for the channel - * @param reason Reason for editing this channel - * @example - * // Edit a channel - * guild.channels.edit('222197033908436994', { name: 'new-channel' }) - * .then(console.log) - * .catch(console.error); - */ - public edit(channel: GuildChannelResolvable, data: ChannelData, reason?: string): Promise; - - /** - * Obtains one or more guild channels from Discord, or the channel cache if they're already available. - * @param id The channel's id - * @param options Additional options for this fetch - * @example - * // Fetch all channels from the guild (excluding threads) - * message.guild.channels.fetch() - * .then(channels => console.log(`There are ${channels.size} channels.`)) - * .catch(console.error); - * @example - * // Fetch a single channel - * message.guild.channels.fetch('222197033908436994') - * .then(channel => console.log(`The channel name is: ${channel.name}`)) - * .catch(console.error); - */ - public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; - public fetch(id?: undefined, options?: BaseFetchOptions): Promise>; - - /** - * Fetches all webhooks for the channel. - * @param channel The channel to fetch webhooks for - * @example - * // Fetch webhooks - * guild.channels.fetchWebhooks('769862166131245066') - * .then(hooks => console.log(`This channel has ${hooks.size} hooks`)) - * .catch(console.error); - */ - public fetchWebhooks(channel: GuildChannelResolvable): Promise>; - - /** - * Sets a new position for the guild channel. - * @param channel The channel to set the position for - * @param position The new position for the guild channel - * @param options Options for setting position - * @example - * // Set a new channel position - * guild.channels.setPosition('222078374472843266', 2) - * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) - * .catch(console.error); - */ - public setPosition( - channel: GuildChannelResolvable, - position: number, - options?: SetChannelPositionOptions - ): Promise; - - /** - * Batch-updates the guild's channels' positions. - * Only one channel's parent can be changed at a time - * @param channelPositions Channel positions to update - * @example - * guild.channels.setPositions([{ channel: channelId, position: newChannelIndex }]) - * .then(guild => console.log(`Updated channel positions for ${guild}`)) - * .catch(console.error); - */ - public setPositions(channelPositions: readonly ChannelPosition[]): Promise; - - /** - * Obtains all active thread channels in the guild from Discord - * @param {} [cache=true] Whether to cache the fetched data - * @example - * // Fetch all threads from the guild - * message.guild.channels.fetchActiveThreads() - * .then(fetched => console.log(`There are ${fetched.threads.size} threads.`)) - * .catch(console.error); - */ - public fetchActiveThreads(cache?: boolean): Promise; - - /** - * Deletes the channel. - * @param channel The channel to delete - * @param reason Reason for deleting this channel - * @example - * // Delete the channel - * guild.channels.delete('858850993013260338', 'making room for new channels') - * .then(console.log) - * .catch(console.error); - */ - public delete(channel: GuildChannelResolvable, reason?: string): Promise; -} diff --git a/src/lib/extensions/discord.js/BushGuildEmoji.ts b/src/lib/extensions/discord.js/BushGuildEmoji.ts deleted file mode 100644 index 9b931bb..0000000 --- a/src/lib/extensions/discord.js/BushGuildEmoji.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { BushClient, BushGuild, BushGuildEmojiRoleManager, BushUser } from '#lib'; -import { GuildEmoji } from 'discord.js'; -import type { RawGuildEmojiData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a custom emoji. - */ -export class BushGuildEmoji extends GuildEmoji { - public declare readonly client: BushClient; - public declare guild: BushGuild; - public declare author: BushUser | null; - - public constructor(client: BushClient, data: RawGuildEmojiData, guild: BushGuild) { - super(client, data, guild); - } -} - -export interface BushGuildEmoji extends GuildEmoji { - get roles(): BushGuildEmojiRoleManager; -} diff --git a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.ts b/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.ts deleted file mode 100644 index 8b069ae..0000000 --- a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { BushClient, BushGuild, BushGuildEmoji, BushRole, BushRoleResolvable } from '#lib'; -import { DataManager, GuildEmojiRoleManager, type Collection, type Snowflake } from 'discord.js'; - -/** - * Manages API methods for roles belonging to emojis and stores their cache. - */ -export declare class BushGuildEmojiRoleManager - extends DataManager - implements GuildEmojiRoleManager -{ - public constructor(emoji: BushGuildEmoji); - public declare readonly client: BushClient; - - /** - * The emoji belonging to this manager - */ - public emoji: BushGuildEmoji; - - /** - * The guild belonging to this manager - */ - public guild: BushGuild; - - /** - * Adds a role (or multiple roles) to the list of roles that can use this emoji. - * @param roleOrRoles The role or roles to add - */ - public add( - roleOrRoles: BushRoleResolvable | readonly BushRoleResolvable[] | Collection - ): Promise; - - /** - * Sets the role(s) that can use this emoji. - * @param roles The roles or role ids to apply - * @example - * // Set the emoji's roles to a single role - * guildEmoji.roles.set(['391156570408615936']) - * .then(console.log) - * .catch(console.error); - * @example - * // Remove all roles from an emoji - * guildEmoji.roles.set([]) - * .then(console.log) - * .catch(console.error); - */ - public set(roles: readonly BushRoleResolvable[] | Collection): Promise; - - /** - * Removes a role (or multiple roles) from the list of roles that can use this emoji. - * @param roleOrRoles The role or roles to remove - */ - public remove( - roleOrRoles: BushRoleResolvable | readonly BushRoleResolvable[] | Collection - ): Promise; -} diff --git a/src/lib/extensions/discord.js/BushGuildManager.ts b/src/lib/extensions/discord.js/BushGuildManager.ts deleted file mode 100644 index 41618e3..0000000 --- a/src/lib/extensions/discord.js/BushGuildManager.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { BushClient, BushGuild, BushGuildResolvable } from '#lib'; -import { - CachedManager, - GuildManager, - type Collection, - type FetchGuildOptions, - type FetchGuildsOptions, - type GuildCreateOptions, - type OAuth2Guild, - type Snowflake -} from 'discord.js'; -import { type RawGuildData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for Guilds and stores their cache. - */ -export declare class BushGuildManager extends CachedManager implements GuildManager { - public constructor(client: BushClient, iterable?: Iterable); - - /** - * Creates a guild. - * This is only available to bots in fewer than 10 guilds. - * @param name The name of the guild - * @param options Options for creating the guild - * @returns The guild that was created - */ - public create(name: string, options?: GuildCreateOptions): Promise; - - /** - * Obtains one or multiple guilds from Discord, or the guild cache if it's already available. - * @param options The guild's id or options - */ - public fetch(options: Snowflake | FetchGuildOptions): Promise; - public fetch(options?: FetchGuildsOptions): Promise>; -} diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts deleted file mode 100644 index 20a1f60..0000000 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ /dev/null @@ -1,1159 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { - BushClientEvents, - Moderation, - ModLogType, - PunishmentTypeDM, - Time, - type BushClient, - type BushGuild, - type BushGuildTextBasedChannel, - type BushGuildTextChannelResolvable, - type BushRole, - type BushThreadChannelResolvable, - type BushUser -} from '#lib'; -import { GuildMember, PermissionFlagsBits, type Partialize, type Role } from 'discord.js'; -import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; -/* eslint-enable @typescript-eslint/no-unused-vars */ - -/** - * Represents a member of a guild on Discord. - */ -export class BushGuildMember extends GuildMember { - public declare readonly client: BushClient; - public declare guild: BushGuild; - public declare user: BushUser; - - public constructor(client: BushClient, data: RawGuildMemberData, guild: BushGuild) { - super(client, data, guild); - } - - /** - * Send a punishment dm to the user. - * @param modlog The modlog case id so the user can make an appeal. - * @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 sendFooter Whether or not to send the guild's punishment footer with the dm. - * @returns Whether or not the dm was sent successfully. - */ - public async bushPunishDM( - punishment: PunishmentTypeDM, - reason?: string | null, - duration?: number, - modlog?: string, - sendFooter = true - ): Promise { - return Moderation.punishDM({ - 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 async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; - const moderator = await util.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( - { - 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) - 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 async bushAddRole(options: AddRoleOptions): Promise { - // 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 util.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({ - 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({ - 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 - ) - client.emit( - 'bushPunishRole', - this, - moderator, - this.guild, - options.reason ?? undefined, - caseID!, - options.duration ?? 0, - options.role as BushRole, - 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 async bushRemoveRole(options: RemoveRoleOptions): Promise { - // 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 util.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({ - 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({ - 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 - ) - client.emit( - 'bushPunishRoleRemove', - this, - moderator, - this.guild, - options.reason ?? undefined, - caseID!, - options.role as BushRole, - 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: BushRole | Role, - moderator?: BushGuildMember - ): 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 async bushMute(options: BushTimedPunishmentOptions): Promise { - // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return muteResponse.MISSING_PERMISSIONS; - const muteRoleID = await this.guild.getSetting('muteRole'); - if (!muteRoleID) return muteResponse.NO_MUTE_ROLE; - const muteRole = this.guild.roles.cache.get(muteRoleID); - if (!muteRole) return muteResponse.MUTE_ROLE_INVALID; - if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) - return muteResponse.MUTE_ROLE_NOT_MANAGEABLE; - - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; - const moderator = await util.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 client.console.warn('muteRoleAddError', e); - client.console.debug(e); - return false; - }); - if (!muteSuccess) return muteResponse.ACTION_ERROR; - - // add modlog entry - const { log: modlog } = await Moderation.createModLogEntry({ - 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({ - 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 - ) - 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 async bushUnmute(options: BushPunishmentOptions): Promise { - // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return unmuteResponse.MISSING_PERMISSIONS; - const muteRoleID = await this.guild.getSetting('muteRole'); - if (!muteRoleID) return unmuteResponse.NO_MUTE_ROLE; - const muteRole = this.guild.roles.cache.get(muteRoleID); - if (!muteRole) return unmuteResponse.MUTE_ROLE_INVALID; - if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) - return unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE; - - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; - const moderator = await util.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 client.console.warn('muteRoleAddError', util.formatError(e, true)); - return false; - }); - if (!muteSuccess) return unmuteResponse.ACTION_ERROR; - - // add modlog entry - const { log: modlog } = await Moderation.createModLogEntry({ - 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({ - 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 - ) - 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 async bushKick(options: BushPunishmentOptions): Promise { - // 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 util.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({ - 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) - 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 async bushBan(options: BushBanOptions): Promise> { - // 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 util.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({ - 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({ - 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 - ) - 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 async bushBlock(options: BlockOptions): Promise { - 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 util.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({ - 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({ - 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({ - 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 - ) - 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 async bushUnblock(options: UnblockOptions): Promise { - const _channel = this.guild.channels.resolve(options.channel); - if (!_channel || (!_channel.isText() && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL; - const channel = _channel as BushGuildTextBasedChannel; - - // 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 util.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({ - 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({ - 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({ - 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 - ) - 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 async bushTimeout(options: BushTimeoutOptions): Promise { - // 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 util.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({ - 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) - 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 async bushRemoveTimeout(options: BushPunishmentOptions): Promise { - // 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 util.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({ - 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) - 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 isOwner(): boolean { - return client.isOwner(this); - } - - /** - * Whether or not the user is a super user of the bot. - */ - public isSuperUser(): boolean { - return 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?: BushGuildMember; - - /** - * 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: BushRole | 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: BushRole | 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: BushGuildTextChannelResolvable | BushThreadChannelResolvable; -} - -/** - * Options for unblocking a user from a channel. - */ -export interface UnblockOptions extends BushPunishmentOptions { - /** - * The channel to unblock the user from. - */ - channel: BushGuildTextChannelResolvable | BushThreadChannelResolvable; -} - -/** - * Punishment options for punishments that can be temporary. - */ -export interface BushTimeoutOptions extends BushPunishmentOptions { - /** - * The duration of the punishment. - */ - duration: number; -} - -type ValueOf = T[keyof T]; - -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; - -/** - * Response returned when adding a role to a user. - */ -export type AddRoleResponse = ValueOf; - -/** - * Response returned when removing a role from a user. - */ -export type RemoveRoleResponse = ValueOf; - -/** - * Response returned when muting a user. - */ -export type MuteResponse = ValueOf; - -/** - * Response returned when unmuting a user. - */ -export type UnmuteResponse = ValueOf; - -/** - * Response returned when kicking a user. - */ -export type KickResponse = ValueOf; - -/** - * Response returned when banning a user. - */ -export type BanResponse = ValueOf; - -/** - * Response returned when blocking a user. - */ -export type BlockResponse = ValueOf; - -/** - * Response returned when unblocking a user. - */ -export type UnblockResponse = ValueOf; - -/** - * Response returned when timing out a user. - */ -export type TimeoutResponse = ValueOf; - -/** - * Response returned when removing a timeout from a user. - */ -export type RemoveTimeoutResponse = ValueOf; - -export type PartialBushGuildMember = Partialize; - -/** - * @typedef {BushClientEvents} VSCodePleaseDontRemove - */ diff --git a/src/lib/extensions/discord.js/BushGuildMemberManager.ts b/src/lib/extensions/discord.js/BushGuildMemberManager.ts deleted file mode 100644 index b0368b5..0000000 --- a/src/lib/extensions/discord.js/BushGuildMemberManager.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember, BushGuildMemberResolvable, BushUser, BushUserResolvable } from '#lib'; -import { - CachedManager, - GuildMemberManager, - type AddGuildMemberOptions, - type BanOptions, - type Collection, - type FetchMemberOptions, - type FetchMembersOptions, - type GuildListMembersOptions, - type GuildMemberEditData, - type GuildPruneMembersOptions, - type GuildSearchMembersOptions, - type Snowflake -} from 'discord.js'; -import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for GuildMembers and stores their cache. - */ -export declare class BushGuildMemberManager - extends CachedManager - implements GuildMemberManager -{ - public constructor(guild: BushGuild, iterable?: Iterable); - public declare readonly client: BushClient; - - /** - * The guild this manager belongs to - */ - public guild: BushGuild; - - /** - * Adds a user to the guild using OAuth2. Requires the `PermissionFlagsBits.CreateInstantInvite` permission. - * @param user The user to add to the guild - * @param options Options for adding the user to the guild - */ - public add( - user: BushUserResolvable, - options: AddGuildMemberOptions & { fetchWhenExisting: false } - ): Promise; - public add(user: BushUserResolvable, options: AddGuildMemberOptions): Promise; - - /** - * Bans a user from the guild. - * @param user The user to ban - * @param options Options for the ban - * @returns Result object will be resolved as specifically as possible. - * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot - * be resolved, the user id will be the result. - * Internally calls the GuildBanManager#create method. - * @example - * // Ban a user by id (or with a user/guild member object) - * guild.members.ban('84484653687267328') - * .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`)) - * .catch(console.error); - */ - public ban(user: BushUserResolvable, options?: BanOptions): Promise; - - /** - * Edits a member of the guild. - * The user must be a member of the guild - * @param user The member to edit - * @param data The data to edit the member with - * @param reason Reason for editing this user - */ - public edit(user: BushUserResolvable, data: GuildMemberEditData, reason?: string): Promise; - - /** - * Fetches member(s) from Discord, even if they're offline. - * @param options If a UserResolvable, the user to fetch. - * If undefined, fetches all members. - * If a query, it limits the results to users with similar usernames. - * @example - * // Fetch all members from a guild - * guild.members.fetch() - * .then(console.log) - * .catch(console.error); - * @example - * // Fetch a single member - * guild.members.fetch('66564597481480192') - * .then(console.log) - * .catch(console.error); - * @example - * // Fetch a single member without checking cache - * guild.members.fetch({ user, force: true }) - * .then(console.log) - * .catch(console.error) - * @example - * // Fetch a single member without caching - * guild.members.fetch({ user, cache: false }) - * .then(console.log) - * .catch(console.error); - * @example - * // Fetch by an array of users including their presences - * guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true }) - * .then(console.log) - * .catch(console.error); - * @example - * // Fetch by query - * guild.members.fetch({ query: 'hydra', limit: 1 }) - * .then(console.log) - * .catch(console.error); - */ - public fetch( - options: BushUserResolvable | FetchMemberOptions | (FetchMembersOptions & { user: BushUserResolvable }) - ): Promise; - public fetch(options?: FetchMembersOptions): Promise>; - - /** - * Kicks a user from the guild. - * The user must be a member of the guild - * @param user The member to kick - * @param reason Reason for kicking - * @returns Result object will be resolved as specifically as possible. - * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot - * be resolved, the user's id will be the result. - * @example - * // Kick a user by id (or with a user/guild member object) - * guild.members.kick('84484653687267328') - * .then(banInfo => console.log(`Kicked ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`)) - * .catch(console.error); - */ - public kick(user: BushUserResolvable, reason?: string): Promise; - - /** - * Lists up to 1000 members of the guild. - * @param options Options for listing members - */ - public list(options?: GuildListMembersOptions): Promise>; - - /** - * Prunes members from the guild based on how long they have been inactive. - * @param options Options for pruning - * @returns The number of members that were/will be kicked - * @example - * // See how many members will be pruned - * guild.members.prune({ dry: true }) - * .then(pruned => console.log(`This will prune ${pruned} people!`)) - * .catch(console.error); - * @example - * // Actually prune the members - * guild.members.prune({ days: 1, reason: 'too many people!' }) - * .then(pruned => console.log(`I just pruned ${pruned} people!`)) - * .catch(console.error); - * @example - * // Include members with a specified role - * guild.members.prune({ days: 7, roles: ['657259391652855808'] }) - * .then(pruned => console.log(`I just pruned ${pruned} people!`)) - * .catch(console.error); - */ - public prune(options: GuildPruneMembersOptions & { dry?: false; count: false }): Promise; - public prune(options?: GuildPruneMembersOptions): Promise; - - /** - * Searches for members in the guild based on a query. - * @param options Options for searching members - */ - public search(options: GuildSearchMembersOptions): Promise>; - - /** - * Unbans a user from the guild. Internally calls the {@link GuildBanManager.remove} method. - * @param user The user to unban - * @param reason Reason for unbanning user - * @returns The user that was unbanned - * @example - * // Unban a user by id (or with a user/guild member object) - * guild.members.unban('84484653687267328') - * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) - * .catch(console.error); - */ - public unban(user: BushUserResolvable, reason?: string): Promise; -} - -export interface BushGuildMemberManager extends CachedManager { - get me(): BushGuildMember | null; -} diff --git a/src/lib/extensions/discord.js/BushMessage.ts b/src/lib/extensions/discord.js/BushMessage.ts deleted file mode 100644 index 48e1792..0000000 --- a/src/lib/extensions/discord.js/BushMessage.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { - BushClient, - BushCommandUtil, - BushGuild, - BushGuildMember, - BushGuildTextBasedChannel, - BushMessageReaction, - BushTextBasedChannel, - BushThreadChannel, - BushUser -} from '#lib'; -import { - Message, - MessageActionRowComponent, - type EmojiIdentifierResolvable, - type If, - type MessageEditOptions, - type MessagePayload, - type Partialize, - type ReplyMessageOptions, - type StartThreadOptions -} from 'discord.js'; -import type { RawMessageData } from 'discord.js/typings/rawDataTypes'; - -export type PartialBushMessage = Partialize< - BushMessage, - 'type' | 'system' | 'pinned' | 'tts', - 'content' | 'cleanContent' | 'author' ->; - -/** - * Represents a message on Discord. - */ -export class BushMessage extends Message { - public declare readonly client: BushClient; - public declare util: BushCommandUtil>; - public declare author: BushUser; - - public constructor(client: BushClient, data: RawMessageData) { - super(client, data); - } -} - -export interface BushMessage extends Message { - get guild(): If; - get member(): BushGuildMember | null; - get channel(): If; - delete(): Promise; - edit(content: string | MessageEditOptions | MessagePayload): Promise; - equals(message: BushMessage, rawData: unknown): boolean; - fetchReference(): Promise; - crosspost(): Promise; - fetch(force?: boolean): Promise; - pin(): Promise; - react(emoji: EmojiIdentifierResolvable): Promise; - removeAttachments(): Promise; - reply(options: string | MessagePayload | ReplyMessageOptions): Promise; - resolveComponent(customId: string): MessageActionRowComponent | null; - startThread(options: StartThreadOptions): Promise; - suppressEmbeds(suppress?: boolean): Promise; - unpin(): Promise; - inGuild(): this is BushMessage & this; -} diff --git a/src/lib/extensions/discord.js/BushMessageManager.ts b/src/lib/extensions/discord.js/BushMessageManager.ts deleted file mode 100644 index edb7982..0000000 --- a/src/lib/extensions/discord.js/BushMessageManager.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { BushMessageResolvable, BushTextBasedChannel, type BushMessage } from '#lib'; -import { - CachedManager, - FetchMessageOptions, - FetchMessagesOptions, - MessageManager, - type Collection, - type EmojiIdentifierResolvable, - type MessageEditOptions, - type MessagePayload, - type Snowflake -} from 'discord.js'; -import type { RawMessageData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for Messages and holds their cache. - */ -export declare class BushMessageManager - extends CachedManager - implements MessageManager -{ - public constructor(channel: BushTextBasedChannel, iterable?: Iterable); - - /** - * The channel that the messages belong to - */ - public channel: BushTextBasedChannel; - - /** - * The cache of Messages - */ - public get cache(): Collection; - - /** - * Publishes a message in an announcement channel to all channels following it, even if it's not cached. - * @param message The message to publish - */ - public crosspost(message: BushMessageResolvable): Promise; - - /** - * Deletes a message, even if it's not cached. - * @param message The message to delete - */ - public delete(message: BushMessageResolvable): Promise; - - /** - * Edits a message, even if it's not cached. - * @param message The message to edit - * @param options The options to edit the message - */ - public edit(message: BushMessageResolvable, options: string | MessagePayload | MessageEditOptions): Promise; - - /** - * Gets a message, or messages, from this channel. - * The returned Collection does not contain reaction users of the messages if they were not cached. - * Those need to be fetched separately in such a case. - * @param message The id of the message to fetch, or query parameters. - * @param options Additional options for this fetch - * @example - * // Get message - * channel.messages.fetch('99539446449315840') - * .then(message => console.log(message.content)) - * .catch(console.error); - * @example - * // Get messages - * channel.messages.fetch({ limit: 10 }) - * .then(messages => console.log(`Received ${messages.size} messages`)) - * .catch(console.error); - * @example - * // Get messages and filter by user id - * channel.messages.fetch() - * .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`)) - * .catch(console.error); - */ - public fetch(options: BushMessageResolvable | FetchMessageOptions): Promise; - public fetch(options?: FetchMessagesOptions): Promise>; - - /** - * Fetches the pinned messages of this channel and returns a collection of them. - * The returned Collection does not contain any reaction data of the messages. - * Those need to be fetched separately. - * @param {} [cache=true] Whether to cache the message(s) - * @example - * // Get pinned messages - * channel.messages.fetchPinned() - * .then(messages => console.log(`Received ${messages.size} messages`)) - * .catch(console.error); - */ - public fetchPinned(cache?: boolean): Promise>; - - /** - * Adds a reaction to a message, even if it's not cached. - * @param message The message to react to - * @param emoji The emoji to react with - */ - public react(message: BushMessageResolvable, emoji: EmojiIdentifierResolvable): Promise; - - /** - * Pins a message to the channel's pinned messages, even if it's not cached. - * @param message The message to pin - */ - public pin(message: BushMessageResolvable): Promise; - - /** - * Unpins a message from the channel's pinned messages, even if it's not cached. - * @param message The message to unpin - */ - public unpin(message: BushMessageResolvable): Promise; -} - -export interface BushFetchMessageOptions extends FetchMessageOptions { - message: BushMessageResolvable; -} diff --git a/src/lib/extensions/discord.js/BushMessageReaction.ts b/src/lib/extensions/discord.js/BushMessageReaction.ts deleted file mode 100644 index 7fe110d..0000000 --- a/src/lib/extensions/discord.js/BushMessageReaction.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { BushClient, BushGuildEmoji, BushMessage, BushReactionEmoji } from '#lib'; -import { MessageReaction, type Partialize } from 'discord.js'; -import type { RawMessageReactionData } from 'discord.js/typings/rawDataTypes'; - -export type PartialBushMessageReaction = Partialize; - -/** - * Represents a reaction to a message. - */ -export class BushMessageReaction extends MessageReaction { - public declare readonly client: BushClient; - - public constructor(client: BushClient, data: RawMessageReactionData, message: BushMessage) { - super(client, data, message); - } -} - -export interface BushMessageReaction extends MessageReaction { - get emoji(): BushGuildEmoji | BushReactionEmoji; -} diff --git a/src/lib/extensions/discord.js/BushModalSubmitInteraction.ts b/src/lib/extensions/discord.js/BushModalSubmitInteraction.ts deleted file mode 100644 index 9bdc9e5..0000000 --- a/src/lib/extensions/discord.js/BushModalSubmitInteraction.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { - BushClient, - BushGuild, - BushGuildCacheMessage, - BushGuildMember, - BushGuildTextBasedChannel, - BushTextBasedChannel, - BushUser -} from '#lib'; -import type { APIInteractionGuildMember, APIModalSubmitInteraction } from 'discord-api-types/v10'; -import { - InteractionDeferUpdateOptions, - InteractionResponse, - InteractionUpdateOptions, - MessagePayload, - ModalSubmitInteraction, - type CacheType, - type CacheTypeReducer -} from 'discord.js'; - -/** - * Represents a button interaction. - */ -export class BushModalSubmitInteraction extends ModalSubmitInteraction { - public declare member: CacheTypeReducer; - public declare user: BushUser; - - public constructor(client: BushClient, data: APIModalSubmitInteraction) { - super(client, data); - } -} - -export interface BushModalSubmitInteraction extends ModalSubmitInteraction { - get channel(): CacheTypeReducer< - Cached, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushTextBasedChannel | null - >; - get guild(): CacheTypeReducer; - inGuild(): this is BushModalSubmitInteraction<'raw' | 'cached'>; - inCachedGuild(): this is BushModalSubmitInteraction<'cached'>; - inRawGuild(): this is BushModalSubmitInteraction<'raw'>; - isFromMessage(): this is BushModalMessageModalSubmitInteraction; -} - -export interface BushModalMessageModalSubmitInteraction - extends ModalSubmitInteraction { - /** - * The message associated with this interaction - */ - message: BushGuildCacheMessage; - - /** - * Updates the original message of the component on which the interaction was received on. - * @param options The options for the updated message - * @example - * // Remove the components from the message - * interaction.update({ - * content: "A component interaction was received", - * components: [] - * }) - * .then(console.log) - * .catch(console.error); - */ - update(options: InteractionUpdateOptions & { fetchReply: true }): Promise>; - update(options: string | MessagePayload | InteractionUpdateOptions): Promise; - - /** - * Defers an update to the message to which the component was attached. - * @param options Options for deferring the update to this interaction - * @example - * // Defer updating and reset the component's loading state - * interaction.deferUpdate() - * .then(console.log) - * .catch(console.error); - */ - deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise>; - deferUpdate(options?: InteractionDeferUpdateOptions): Promise; - - /** - * Indicates whether this interaction is received from a guild. - */ - inGuild(): this is BushModalMessageModalSubmitInteraction<'raw' | 'cached'>; - - /** - * Indicates whether or not this interaction is both cached and received from a guild. - */ - inCachedGuild(): this is BushModalMessageModalSubmitInteraction<'cached'>; - - /** - * Indicates whether or not this interaction is received from an uncached guild. - */ - inRawGuild(): this is BushModalMessageModalSubmitInteraction<'raw'>; -} diff --git a/src/lib/extensions/discord.js/BushNewsChannel.ts b/src/lib/extensions/discord.js/BushNewsChannel.ts deleted file mode 100644 index e262188..0000000 --- a/src/lib/extensions/discord.js/BushNewsChannel.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { BushGuild, BushGuildMember, BushMessageManager, BushThreadManager } from '#lib'; -import { NewsChannel, type AllowedThreadTypeForNewsChannel, type Collection, type Snowflake } from 'discord.js'; - -/** - * Represents a guild news channel on Discord. - */ -export class BushNewsChannel extends NewsChannel { - public declare threads: BushThreadManager; - public declare guild: BushGuild; - public declare messages: BushMessageManager; -} - -export interface BushNewsChannel extends NewsChannel { - get members(): Collection; -} diff --git a/src/lib/extensions/discord.js/BushPresence.ts b/src/lib/extensions/discord.js/BushPresence.ts deleted file mode 100644 index 40873ac..0000000 --- a/src/lib/extensions/discord.js/BushPresence.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember, BushUser } from '#lib'; -import { Presence } from 'discord.js'; -import type { RawPresenceData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a user's presence. - */ -export class BushPresence extends Presence { - public declare guild: BushGuild | null; - - public constructor(client: BushClient, data?: RawPresenceData) { - super(client, data); - } -} - -export interface BushPresence extends Presence { - get member(): BushGuildMember | null; - get user(): BushUser | null; -} diff --git a/src/lib/extensions/discord.js/BushReactionEmoji.ts b/src/lib/extensions/discord.js/BushReactionEmoji.ts deleted file mode 100644 index b2a7eb0..0000000 --- a/src/lib/extensions/discord.js/BushReactionEmoji.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { BushMessageReaction } from '#lib'; -import { ReactionEmoji } from 'discord.js'; -import type { RawReactionEmojiData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis - * will use this class opposed to the Emoji class when the client doesn't know enough - * information about them. - */ -export class BushReactionEmoji extends ReactionEmoji { - public declare reaction: BushMessageReaction; - - public constructor(reaction: BushMessageReaction, emoji: RawReactionEmojiData) { - super(reaction, emoji); - } -} diff --git a/src/lib/extensions/discord.js/BushRole.ts b/src/lib/extensions/discord.js/BushRole.ts deleted file mode 100644 index a9575bd..0000000 --- a/src/lib/extensions/discord.js/BushRole.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember } from '#lib'; -import { Role, type Collection, type Snowflake } from 'discord.js'; -import type { RawRoleData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a role on Discord. - */ -export class BushRole extends Role { - public declare guild: BushGuild; - - public constructor(client: BushClient, data: RawRoleData, guild: BushGuild) { - super(client, data, guild); - } -} - -export interface BushRole extends Role { - get members(): Collection; -} diff --git a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts deleted file mode 100644 index 66a5ea9..0000000 --- a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib'; -import type { APIInteractionGuildMember } from 'discord-api-types/v10'; -import { SelectMenuInteraction, type CacheType, type CacheTypeReducer } from 'discord.js'; -import type { RawMessageSelectMenuInteractionData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a select menu interaction. - */ -export class BushSelectMenuInteraction extends SelectMenuInteraction { - public declare member: CacheTypeReducer; - public declare user: BushUser; - - public constructor(client: BushClient, data: RawMessageSelectMenuInteractionData) { - super(client, data); - } -} - -export interface BushSelectMenuInteraction extends SelectMenuInteraction { - get channel(): CacheTypeReducer< - Cached, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushGuildTextBasedChannel | null, - BushTextBasedChannel | null - >; - get guild(): CacheTypeReducer; - inGuild(): this is BushSelectMenuInteraction<'raw' | 'cached'>; - inCachedGuild(): this is BushSelectMenuInteraction<'cached'>; - inRawGuild(): this is BushSelectMenuInteraction<'raw'>; -} diff --git a/src/lib/extensions/discord.js/BushStageChannel.ts b/src/lib/extensions/discord.js/BushStageChannel.ts deleted file mode 100644 index 983bd56..0000000 --- a/src/lib/extensions/discord.js/BushStageChannel.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { BushCategoryChannel, BushGuild, BushGuildMember, BushStageInstance } from '#lib'; -import { StageChannel, type Collection, type Snowflake } from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a guild stage channel on Discord. - */ -export class BushStageChannel extends StageChannel { - public declare guild: BushGuild; - - public constructor(guild: BushGuild, data?: RawGuildChannelData) { - super(guild, data); - } -} - -export interface BushStageChannel extends StageChannel { - get members(): Collection; - get parent(): BushCategoryChannel | null; - get stageInstance(): BushStageInstance | null; -} diff --git a/src/lib/extensions/discord.js/BushStageInstance.ts b/src/lib/extensions/discord.js/BushStageInstance.ts deleted file mode 100644 index 96453a7..0000000 --- a/src/lib/extensions/discord.js/BushStageInstance.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { BushClient, BushGuild, BushStageChannel } from '#lib'; -import { StageInstance } from 'discord.js'; -import type { RawStageInstanceData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a stage instance. - */ -export class BushStageInstance extends StageInstance { - public constructor(client: BushClient, data: RawStageInstanceData, channel: BushStageChannel) { - super(client, data, channel); - } -} - -export interface BushStageInstance extends StageInstance { - get channel(): BushStageChannel | null; - get guild(): BushGuild | null; -} diff --git a/src/lib/extensions/discord.js/BushTextChannel.ts b/src/lib/extensions/discord.js/BushTextChannel.ts deleted file mode 100644 index 575de20..0000000 --- a/src/lib/extensions/discord.js/BushTextChannel.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - BushCategoryChannel, - BushDMChannel, - BushGuild, - BushGuildBasedChannel, - BushMessageManager, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushThreadChannel, - BushThreadManager, - BushVoiceBasedChannel, - BushVoiceChannel -} from '#lib'; -import { PartialGroupDMChannel, TextChannel, type AllowedThreadTypeForTextChannel } from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a guild text channel on Discord. - */ -export class BushTextChannel extends TextChannel { - public declare guild: BushGuild; - public declare messages: BushMessageManager; - public declare threads: BushThreadManager; - - public constructor(guild: BushGuild, data?: RawGuildChannelData) { - super(guild, data); - } -} - -export interface BushTextChannel extends TextChannel { - isText(): this is BushTextChannel; - isDM(): this is BushDMChannel; - isDMBased(): this is PartialGroupDMChannel | BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushGuildBasedChannel & BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushThreadChannel.ts b/src/lib/extensions/discord.js/BushThreadChannel.ts deleted file mode 100644 index 8b941f9..0000000 --- a/src/lib/extensions/discord.js/BushThreadChannel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - BushCategoryChannel, - BushClient, - BushDMChannel, - BushGuild, - BushGuildBasedChannel, - BushGuildMember, - BushMessageManager, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadMemberManager, - BushVoiceBasedChannel, - BushVoiceChannel -} from '#lib'; -import { PartialGroupDMChannel, ThreadChannel, type Collection, type Snowflake } from 'discord.js'; -import type { RawThreadChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a thread channel on Discord. - */ -export class BushThreadChannel extends ThreadChannel { - public declare guild: BushGuild; - public declare messages: BushMessageManager; - public declare members: BushThreadMemberManager; - public declare readonly client: BushClient; - - public constructor(guild: BushGuild, data?: RawThreadChannelData, client?: BushClient, fromInteraction?: boolean) { - super(guild, data, client, fromInteraction); - } -} - -export interface BushThreadChannel extends ThreadChannel { - get guildMembers(): Collection; - get parent(): BushTextChannel | BushNewsChannel | null; - isText(): this is BushTextChannel; - isDM(): this is BushDMChannel; - isDMBased(): this is PartialGroupDMChannel | BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushGuildBasedChannel & BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushThreadManager.ts b/src/lib/extensions/discord.js/BushThreadManager.ts deleted file mode 100644 index 0748a4d..0000000 --- a/src/lib/extensions/discord.js/BushThreadManager.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { BushFetchedThreads, BushThreadChannel } from '#lib'; -import { - CachedManager, - NewsChannel, - TextChannel, - ThreadManager, - type BaseFetchOptions, - type FetchArchivedThreadOptions, - type FetchThreadsOptions, - type Snowflake, - type ThreadChannelResolvable, - type ThreadCreateOptions -} from 'discord.js'; -import type { RawThreadChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for {@link BushThreadChannel} objects and stores their cache. - */ -export declare class BushThreadManager - extends CachedManager - implements ThreadManager -{ - public constructor(channel: TextChannel | NewsChannel, iterable?: Iterable); - - /** - * The channel this Manager belongs to - */ - public channel: TextChannel | NewsChannel; - - /** - * Creates a new thread in the channel. - * @param options Options to create a new thread - * @example - * // Create a new public thread - * channel.threads - * .create({ - * name: 'food-talk', - * autoArchiveDuration: 60, - * reason: 'Needed a separate thread for food', - * }) - * .then(threadChannel => console.log(threadChannel)) - * .catch(console.error); - * @example - * // Create a new private thread - * channel.threads - * .create({ - * name: 'mod-talk', - * autoArchiveDuration: 60, - * type: 'GuildPrivateThread', - * reason: 'Needed a separate thread for moderation', - * }) - * .then(threadChannel => console.log(threadChannel)) - * .catch(console.error); - */ - public create(options: ThreadCreateOptions): Promise; - - /** - * Obtains a thread from Discord, or the channel cache if it's already available. - * @param options The options to fetch threads. If it is a - * ThreadChannelResolvable then the specified thread will be fetched. Fetches all active threads if `undefined` - * @param cacheOptions Additional options for this fetch. The `force` field gets ignored - * if `options` is not a {@link ThreadChannelResolvable} - * @example - * // Fetch a thread by its id - * channel.threads.fetch('831955138126104859') - * .then(channel => console.log(channel.name)) - * .catch(console.error); - */ - public fetch(options: ThreadChannelResolvable, cacheOptions?: BaseFetchOptions): Promise; - public fetch(options?: FetchThreadsOptions, cacheOptions?: { cache?: boolean }): Promise; - - /** - * Obtains a set of archived threads from Discord, requires `READ_MESSAGE_HISTORY` in the parent channel. - * @param options The options to fetch archived threads - * @param cache Whether to cache the new thread objects if they aren't already - */ - public fetchArchived(options?: FetchArchivedThreadOptions, cache?: boolean): Promise; - - /** - * Obtains the accessible active threads from Discord, requires `READ_MESSAGE_HISTORY` in the parent channel. - * @param cache Whether to cache the new thread objects if they aren't already - */ - public fetchActive(cache?: boolean): Promise; -} diff --git a/src/lib/extensions/discord.js/BushThreadMember.ts b/src/lib/extensions/discord.js/BushThreadMember.ts deleted file mode 100644 index 90c9c9b..0000000 --- a/src/lib/extensions/discord.js/BushThreadMember.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { BushGuildMember, BushThreadChannel, BushUser } from '#lib'; -import { ThreadMember } from 'discord.js'; -import type { RawThreadMemberData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a Member for a Thread. - */ -export class BushThreadMember extends ThreadMember { - public declare thread: BushThreadChannel; - - public constructor(thread: BushThreadChannel, data?: RawThreadMemberData) { - super(thread, data); - } -} - -export interface BushThreadMember extends ThreadMember { - get guildMember(): BushGuildMember | null; - get user(): BushUser | null; -} diff --git a/src/lib/extensions/discord.js/BushThreadMemberManager.ts b/src/lib/extensions/discord.js/BushThreadMemberManager.ts deleted file mode 100644 index d183b30..0000000 --- a/src/lib/extensions/discord.js/BushThreadMemberManager.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { BushClient, BushThreadChannel, BushThreadMember, BushThreadMemberResolvable, BushUserResolvable } from '#lib'; -import { - CachedManager, - ThreadMemberManager, - type BaseFetchOptions, - type Collection, - type Snowflake, - type UserResolvable -} from 'discord.js'; -import type { RawThreadMemberData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for GuildMembers and stores their cache. - */ -export declare class BushThreadMemberManager - extends CachedManager - implements ThreadMemberManager -{ - public constructor(thread: BushThreadChannel, iterable?: Iterable); - public declare readonly client: BushClient; - - /** - * The thread this manager belongs to - */ - public thread: BushThreadChannel; - - /** - * Adds a member to the thread. - * @param member The member to add - * @param reason The reason for adding this member - */ - public add(member: UserResolvable | '@me', reason?: string): Promise; - - /** - * Fetches member(s) for the thread from Discord, requires access to the `GatewayIntentBits.GuildMembers` gateway intent. - * @param options Additional options for this fetch, when a `boolean` is provided - * all members are fetched with `options.cache` set to the boolean value - */ - public fetch(options?: BushThreadMemberFetchOptions): Promise; - public fetch(cache?: boolean): Promise>; - - /** - * Remove a user from the thread. - * @param id The id of the member to remove - * @param reason The reason for removing this member from the thread - */ - public remove(id: Snowflake | '@me', reason?: string): Promise; -} - -export interface BushThreadMemberManager extends CachedManager { - /** - * The client user as a ThreadMember of this ThreadChannel - */ - get me(): BushThreadMember | null; -} - -export interface BushThreadMemberFetchOptions extends BaseFetchOptions { - /** - * The specific user to fetch from the thread - */ - member?: BushUserResolvable; -} diff --git a/src/lib/extensions/discord.js/BushUser.ts b/src/lib/extensions/discord.js/BushUser.ts deleted file mode 100644 index 27ef2b2..0000000 --- a/src/lib/extensions/discord.js/BushUser.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { BushClient, BushDMChannel } from '#lib'; -import { User, type Partialize } from 'discord.js'; -import type { RawUserData } from 'discord.js/typings/rawDataTypes'; - -export type PartialBushUser = Partialize; - -/** - * Represents a user on Discord. - */ -export class BushUser extends User { - public declare readonly client: BushClient; - - public constructor(client: BushClient, data: RawUserData) { - super(client, data); - } - - /** - * Indicates whether the user is an owner of the bot. - */ - public isOwner(): boolean { - return client.isOwner(this); - } - - /** - * Indicates whether the user is a superuser of the bot. - */ - public isSuperUser(): boolean { - return client.isSuperUser(this); - } -} - -export interface BushUser extends User { - get dmChannel(): BushDMChannel | null; -} diff --git a/src/lib/extensions/discord.js/BushUserManager.ts b/src/lib/extensions/discord.js/BushUserManager.ts deleted file mode 100644 index c26dbde..0000000 --- a/src/lib/extensions/discord.js/BushUserManager.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { BushClient, BushDMChannel, BushUser, BushUserResolvable } from '#lib'; -import { - CachedManager, - Message, - MessageOptions, - MessagePayload, - UserFlagsBitField, - UserManager, - type BaseFetchOptions, - type Snowflake -} from 'discord.js'; -import type { RawUserData } from 'discord.js/typings/rawDataTypes'; - -/** - * Manages API methods for users and stores their cache. - */ -export declare class BushUserManager extends CachedManager implements UserManager { - private constructor(client: BushClient, iterable?: Iterable); - - /** - * The DM between the client's user and a user - * @param userId The user id - * @private - */ - public dmChannel(userId: Snowflake): BushDMChannel | null; - - /** - * Creates a {@link BushDMChannel} between the client and a user. - * @param user The UserResolvable to identify - * @param options Additional options for this fetch - */ - public createDM(user: BushUserResolvable, options?: BaseFetchOptions): Promise; - - /** - * Deletes a {@link BushDMChannel} (if one exists) between the client and a user. Resolves with the channel if successful. - * @param user The UserResolvable to identify - */ - public deleteDM(user: BushUserResolvable): Promise; - - /** - * Obtains a user from Discord, or the user cache if it's already available. - * @param user The user to fetch - * @param options Additional options for this fetch - */ - public fetch(user: BushUserResolvable, options?: BaseFetchOptions): Promise; - - /** - * Fetches a user's flags. - * @param user The UserResolvable to identify - * @param options Additional options for this fetch - */ - public fetchFlags(user: BushUserResolvable, options?: BaseFetchOptions): Promise; - - /** - * Sends a message to a user. - * @param user The UserResolvable to identify - * @param options The options to provide - */ - public send(user: BushUserResolvable, options: string | MessagePayload | MessageOptions): Promise; -} diff --git a/src/lib/extensions/discord.js/BushVoiceChannel.ts b/src/lib/extensions/discord.js/BushVoiceChannel.ts deleted file mode 100644 index 6966727..0000000 --- a/src/lib/extensions/discord.js/BushVoiceChannel.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { - BushCategoryChannel, - BushClient, - BushDMChannel, - BushGuild, - BushGuildBasedChannel, - BushGuildMember, - BushNewsChannel, - BushStageChannel, - BushTextBasedChannel, - BushTextChannel, - BushThreadChannel, - BushVoiceBasedChannel -} from '#lib'; -import { VoiceChannel, type Collection, type Snowflake } from 'discord.js'; -import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents a guild voice channel on Discord. - */ -export class BushVoiceChannel extends VoiceChannel { - public declare readonly client: BushClient; - - public constructor(guild: BushGuild, data?: RawGuildChannelData) { - super(guild, data); - } -} - -export interface BushVoiceChannel extends VoiceChannel { - get members(): Collection; - isText(): this is BushTextChannel; - isDM(): this is BushDMChannel; - isVoice(): this is BushVoiceChannel; - isCategory(): this is BushCategoryChannel; - isNews(): this is BushNewsChannel; - isThread(): this is BushThreadChannel; - isStage(): this is BushStageChannel; - isTextBased(): this is BushGuildBasedChannel & BushTextBasedChannel; - isVoiceBased(): this is BushVoiceBasedChannel; -} diff --git a/src/lib/extensions/discord.js/BushVoiceState.ts b/src/lib/extensions/discord.js/BushVoiceState.ts deleted file mode 100644 index bbcdfa8..0000000 --- a/src/lib/extensions/discord.js/BushVoiceState.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { BushClient, BushGuild, BushGuildMember, BushVoiceBasedChannel } from '#lib'; -import { VoiceState } from 'discord.js'; -import type { RawVoiceStateData } from 'discord.js/typings/rawDataTypes'; - -/** - * Represents the voice state for a Guild Member. - */ -export class BushVoiceState extends VoiceState { - public declare readonly client: BushClient; - public declare guild: BushGuild; - - public constructor(guild: BushGuild, data: RawVoiceStateData) { - super(guild, data); - } -} - -export interface BushVoiceState extends VoiceState { - get channel(): BushVoiceBasedChannel | null; - get getmember(): BushGuildMember | null; -} diff --git a/src/lib/extensions/discord.js/ExtendedGuild.ts b/src/lib/extensions/discord.js/ExtendedGuild.ts new file mode 100644 index 0000000..b8b7b22 --- /dev/null +++ b/src/lib/extensions/discord.js/ExtendedGuild.ts @@ -0,0 +1,870 @@ +import { + AllowedMentions, + banResponse, + dmResponse, + permissionsResponse, + punishmentEntryRemove, + type BanResponse, + type GuildFeatures, + type GuildLogType, + type GuildModel +} from '#lib'; +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 { Moderation } from '../../common/util/Moderation.js'; +import { Guild as GuildDB } from '../../models/instance/Guild.js'; +import { ModLogType } from '../../models/instance/ModLog.js'; + +declare module 'discord.js' { + export interface Guild { + /** + * Checks if the guild has a certain custom feature. + * @param feature The feature to check for + */ + hasFeature(feature: GuildFeatures): Promise; + /** + * Adds a custom feature to the guild. + * @param feature The feature to add + * @param moderator The moderator responsible for adding a feature + */ + addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; + /** + * Removes a custom feature from the guild. + * @param feature The feature to remove + * @param moderator The moderator responsible for removing a feature + */ + removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; + /** + * Makes a custom feature the opposite of what it was before + * @param feature The feature to toggle + * @param moderator The moderator responsible for toggling a feature + */ + toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise; + /** + * Fetches a custom setting for the guild + * @param setting The setting to get + */ + getSetting(setting: K): Promise; + /** + * Sets a custom setting for the guild + * @param setting The setting to change + * @param value The value to change the setting to + * @param moderator The moderator to responsible for changing the setting + */ + setSetting>( + setting: K, + value: GuildModel[K], + moderator?: GuildMember + ): Promise; + /** + * Get a the log channel configured for a certain log type. + * @param logType The type of log channel to get. + * @returns Either the log channel or undefined if not configured. + */ + getLogChannel(logType: GuildLogType): Promise; + /** + * Sends a message to the guild's specified logging channel + * @param logType The corresponding channel that the message will be sent to + * @param message The parameters for {@link BushTextChannel.send} + */ + sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise; + /** + * Sends a formatted error message in a guild's error log channel + * @param title The title of the error embed + * @param message The description of the error embed + */ + error(title: string, message: string): Promise; + /** + * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. + * @param options Options for banning the user. + * @returns A string status message of the ban. + */ + bushBan(options: GuildBushBanOptions): Promise; + /** + * {@link bushBan} with less resolving and checks + * @param options Options for banning the user. + * @returns A string status message of the ban. + * **Preconditions:** + * - {@link me} has the `BanMembers` permission + * **Warning:** + * - Doesn't emit bushBan Event + */ + massBanOne(options: GuildMassBanOneOptions): Promise; + /** + * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry. + * @param options Options for unbanning the user. + * @returns A status message of the unban. + */ + bushUnban(options: GuildBushUnbanOptions): Promise; + /** + * Denies send permissions in specified channels + * @param options The options for locking down the guild + */ + lockdown(options: LockdownOptions): Promise; + quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise; + } +} + +/** + * Represents a guild (or a server) on Discord. + * It's recommended to see if a guild is available before performing operations or reading data from it. You can + * check this with {@link ExtendedGuild.available}. + */ +export class ExtendedGuild extends Guild { + /** + * Checks if the guild has a certain custom feature. + * @param feature The feature to check for + */ + public override async hasFeature(feature: GuildFeatures): Promise { + const features = await this.getSetting('enabledFeatures'); + return features.includes(feature); + } + + /** + * Adds a custom feature to the guild. + * @param feature The feature to add + * @param moderator The moderator responsible for adding a feature + */ + public override async addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { + const features = await this.getSetting('enabledFeatures'); + const newFeatures = util.addOrRemoveFromArray('add', features, feature); + return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; + } + + /** + * Removes a custom feature from the guild. + * @param feature The feature to remove + * @param moderator The moderator responsible for removing a feature + */ + public override async removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { + const features = await this.getSetting('enabledFeatures'); + const newFeatures = util.addOrRemoveFromArray('remove', features, feature); + return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; + } + + /** + * Makes a custom feature the opposite of what it was before + * @param feature The feature to toggle + * @param moderator The moderator responsible for toggling a feature + */ + public override async toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise { + return (await this.hasFeature(feature)) + ? await this.removeFeature(feature, moderator) + : await this.addFeature(feature, moderator); + } + + /** + * Fetches a custom setting for the guild + * @param setting The setting to get + */ + public override async getSetting(setting: K): Promise { + return ( + client.cache.guilds.get(this.id)?.[setting] ?? + ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting] + ); + } + + /** + * Sets a custom setting for the guild + * @param setting The setting to change + * @param value The value to change the setting to + * @param moderator The moderator to responsible for changing the setting + */ + public override async setSetting>( + setting: K, + value: GuildDB[K], + moderator?: GuildMember + ): Promise { + const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }); + const oldValue = row[setting] as GuildDB[K]; + row[setting] = value; + client.cache.guilds.set(this.id, row.toJSON() as GuildDB); + client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator); + return await row.save(); + } + + /** + * Get a the log channel configured for a certain log type. + * @param logType The type of log channel to get. + * @returns Either the log channel or undefined if not configured. + */ + public override async getLogChannel(logType: GuildLogType): Promise { + const channelId = (await this.getSetting('logChannels'))[logType]; + if (!channelId) return undefined; + return ( + (this.channels.cache.get(channelId) as TextChannel | undefined) ?? + ((await this.channels.fetch(channelId)) as TextChannel | null) ?? + undefined + ); + } + + /** + * Sends a message to the guild's specified logging channel + * @param logType The corresponding channel that the message will be sent to + * @param message The parameters for {@link BushTextChannel.send} + */ + public override async sendLogChannel( + logType: GuildLogType, + message: string | MessagePayload | MessageOptions + ): Promise { + const logChannel = await this.getLogChannel(logType); + if (!logChannel || !logChannel.isTextBased()) return; + if ( + !logChannel + .permissionsFor(this.members.me!.id) + ?.has([PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.EmbedLinks]) + ) + return; + + return await logChannel.send(message).catch(() => null); + } + + /** + * Sends a formatted error message in a guild's error log channel + * @param title The title of the error embed + * @param message The description of the error embed + */ + public override async error(title: string, message: string): Promise { + void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); + void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] }); + } + + /** + * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. + * @param options Options for banning the user. + * @returns A string status message of the ban. + */ + public override async bushBan(options: GuildBushBanOptions): Promise { + // checks + if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; + + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; + const user = await util.resolveNonCachedUser(options.user); + const moderator = client.users.resolve(options.moderator ?? 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({ + 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({ + 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({ + 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)) + 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 { + if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED; + + const ret = await (async () => { + // add modlog entry + const { log: modlog } = await Moderation.createModLogEntrySimple({ + 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({ + 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({ + 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 { + // 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 util.resolveNonCachedUser(options.user); + const moderator = client.users.resolve(options.moderator ?? 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({ + 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({ + type: 'ban', + user: user.id, + guild: this + }); + if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; + + // dm user + dmSuccessEvent = await Moderation.punishDM({ + 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 + ) + ) + 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 { + 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(); + const success = new Collection(); + const ret = await (async (): Promise => { + 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 ? util.colors.Green : util.colors.Red, + timestamp: new Date().toISOString() + } + ] + }); + } + } + + if (errors.size) return errors; + else return `success: ${success.filter((c) => c === true).size}`; + })(); + + client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all); + return ret; + } + + public override async quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise { + 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(client, rawQuote); + + const target = channel instanceof ThreadChannel ? channel.parent : channel; + if (!target) return null; + + const webhooks: Collection = 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: `${client.user!.username} Quotes #${target.name}`, + avatar: client.user!.displayAvatarURL({ size: 2048 }), + reason: 'Creating a webhook for quoting' + }) + .catch(() => null); + + if (!webhook) return null; + + const sendOptions: Omit = {}; + + 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)) + : 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 = `${util.emojis.error} Cannot resolve recipient.`; + break; + } + + if (quote.channel.isThread()) { + const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username; + sendOptions.content = `${util.emojis.join} ${displayName} added ${recipientDisplay} to the thread.`; + } else { + // this should never happen + sendOptions.content = `${util.emojis.join} ${displayName} added ${recipient.username} to the group.`; + } + + break; + } + case MessageType.RecipientRemove: { + const recipient = rawQuote.mentions[0]; + if (!recipient) { + sendOptions.content = `${util.emojis.error} Cannot resolve recipient.`; + break; + } + + if (quote.channel.isThread()) { + const recipientDisplay = quote.guild?.members.cache.get(recipient.id)?.displayName ?? recipient.username; + sendOptions.content = `${util.emojis.leave} ${displayName} removed ${recipientDisplay} from the thread.`; + } else { + // this should never happen + sendOptions.content = `${util.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.GuildMemberJoin: { + 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 = `${util.emojis.join} ${message}`; + break; + } + case MessageType.UserPremiumGuildSubscription: + sendOptions.content = `<:NitroBoost:585558042309820447> ${displayName} just boosted the server${ + quote.content ? ` **${quote.content}** times` : '' + }!`; + + break; + case MessageType.UserPremiumGuildSubscriptionTier1: + case MessageType.UserPremiumGuildSubscriptionTier2: + case MessageType.UserPremiumGuildSubscriptionTier3: + 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; + case MessageType.ChannelIconChange: + case MessageType.Call: + default: + sendOptions.content = `${util.emojis.error} I cannot quote **${ + 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[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; + +/** + * 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; diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts new file mode 100644 index 0000000..28acc1a --- /dev/null +++ b/src/lib/extensions/discord.js/ExtendedGuildMember.ts @@ -0,0 +1,1240 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { BushClientEvents, Moderation, ModLogType, PunishmentTypeDM, Time } from '#lib'; +import { + ChannelType, + GuildChannelResolvable, + GuildMember, + GuildTextBasedChannel, + PermissionFlagsBits, + 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; + /** + * 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; + /** + * 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; + /** + * 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; + /** + * 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; + /** + * 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; + /** + * 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>; + /** + * Prevents a user from speaking in a channel. + * @param options Options for blocking the user. + */ + bushBlock(options: BlockOptions): Promise; + /** + * Allows a user to speak in a channel. + * @param options Options for unblocking the user. + */ + bushUnblock(options: UnblockOptions): Promise; + /** + * Mutes a user using discord's timeout feature. + * @param options Options for timing out the user. + */ + bushTimeout(options: BushTimeoutOptions): Promise; + /** + * Removes a timeout from a user. + * @param options Options for removing the timeout. + */ + bushRemoveTimeout(options: BushPunishmentOptions): Promise; + /** + * 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 { + return Moderation.punishDM({ + 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 util.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( + { + 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) + 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 { + // 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 util.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({ + 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({ + 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 + ) + 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 { + // 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 util.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({ + 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({ + 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 + ) + 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 { + // checks + if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return muteResponse.MISSING_PERMISSIONS; + const muteRoleID = await this.guild.getSetting('muteRole'); + if (!muteRoleID) return muteResponse.NO_MUTE_ROLE; + const muteRole = this.guild.roles.cache.get(muteRoleID); + if (!muteRole) return muteResponse.MUTE_ROLE_INVALID; + if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) + return muteResponse.MUTE_ROLE_NOT_MANAGEABLE; + + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; + const moderator = await util.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 client.console.warn('muteRoleAddError', e); + client.console.debug(e); + return false; + }); + if (!muteSuccess) return muteResponse.ACTION_ERROR; + + // add modlog entry + const { log: modlog } = await Moderation.createModLogEntry({ + 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({ + 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 + ) + 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 { + // checks + if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return unmuteResponse.MISSING_PERMISSIONS; + const muteRoleID = await this.guild.getSetting('muteRole'); + if (!muteRoleID) return unmuteResponse.NO_MUTE_ROLE; + const muteRole = this.guild.roles.cache.get(muteRoleID); + if (!muteRole) return unmuteResponse.MUTE_ROLE_INVALID; + if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) + return unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE; + + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; + const moderator = await util.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 client.console.warn('muteRoleAddError', util.formatError(e, true)); + return false; + }); + if (!muteSuccess) return unmuteResponse.ACTION_ERROR; + + // add modlog entry + const { log: modlog } = await Moderation.createModLogEntry({ + 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({ + 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 + ) + 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 { + // 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 util.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({ + 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) + 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> { + // 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 util.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({ + 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({ + 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 + ) + 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 { + 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 util.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({ + 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({ + 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({ + 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 + ) + 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 { + 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 util.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({ + 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({ + 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({ + 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 + ) + 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 { + // 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 util.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({ + 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) + 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 { + // 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 util.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({ + 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) + 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 client.isOwner(this); + } + + /** + * Whether or not the user is a super user of the bot. + */ + public override isSuperUser(): boolean { + return 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; +} + +type ValueOf = T[keyof T]; + +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; + +/** + * Response returned when adding a role to a user. + */ +export type AddRoleResponse = ValueOf; + +/** + * Response returned when removing a role from a user. + */ +export type RemoveRoleResponse = ValueOf; + +/** + * Response returned when muting a user. + */ +export type MuteResponse = ValueOf; + +/** + * Response returned when unmuting a user. + */ +export type UnmuteResponse = ValueOf; + +/** + * Response returned when kicking a user. + */ +export type KickResponse = ValueOf; + +/** + * Response returned when banning a user. + */ +export type BanResponse = ValueOf; + +/** + * Response returned when blocking a user. + */ +export type BlockResponse = ValueOf; + +/** + * Response returned when unblocking a user. + */ +export type UnblockResponse = ValueOf; + +/** + * Response returned when timing out a user. + */ +export type TimeoutResponse = ValueOf; + +/** + * Response returned when removing a timeout from a user. + */ +export type RemoveTimeoutResponse = ValueOf; + +/** + * @typedef {BushClientEvents} VSCodePleaseDontRemove + */ diff --git a/src/lib/extensions/discord.js/ExtendedMessage.ts b/src/lib/extensions/discord.js/ExtendedMessage.ts new file mode 100644 index 0000000..4431077 --- /dev/null +++ b/src/lib/extensions/discord.js/ExtendedMessage.ts @@ -0,0 +1,12 @@ +import { CommandUtil } from 'discord-akairo'; +import { Message, type Client } from 'discord.js'; +import { type RawMessageData } from 'discord.js/typings/rawDataTypes.js'; + +export class ExtendedMessage extends Message { + public declare util: CommandUtil; + + 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 new file mode 100644 index 0000000..556ab85 --- /dev/null +++ b/src/lib/extensions/discord.js/ExtendedUser.ts @@ -0,0 +1,35 @@ +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; + +/** + * 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 client.isOwner(this); + } + + /** + * Indicates whether the user is a superuser of the bot. + */ + public override isSuperUser(): boolean { + return client.isSuperUser(this); + } +} diff --git a/src/lib/extensions/discord.js/other.ts b/src/lib/extensions/discord.js/other.ts deleted file mode 100644 index 0560ffc..0000000 --- a/src/lib/extensions/discord.js/other.ts +++ /dev/null @@ -1,188 +0,0 @@ -import type { - BushApplicationCommand, - BushCategoryChannel, - BushDMChannel, - BushGuild, - BushGuildEmoji, - BushGuildMember, - BushMessage, - BushNewsChannel, - BushReactionEmoji, - BushRole, - BushStageChannel, - BushTextChannel, - BushThreadChannel, - BushThreadMember, - BushUser, - BushVoiceChannel, - PartialBushDMChannel -} from '#lib'; -import { APIMessage } from 'discord-api-types/v10'; -import type { - ApplicationCommandResolvable, - CacheType, - CacheTypeReducer, - ChannelResolvable, - ChannelType, - Collection, - EmojiIdentifierResolvable, - EmojiResolvable, - FetchedThreads, - GuildChannelResolvable, - GuildMemberResolvable, - GuildTextChannelResolvable, - MessageResolvable, - PartialGroupDMChannel, - RoleResolvable, - Snowflake, - ThreadChannelResolvable, - ThreadMemberResolvable, - UserResolvable -} from 'discord.js'; - -/** - * Data that resolves to give a ThreadMember object. - */ -export type BushThreadMemberResolvable = ThreadMemberResolvable | BushThreadMember | BushUserResolvable; - -/** - * Data that resolves to give a User object. - */ -export type BushUserResolvable = UserResolvable | BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember; - -/** - * Data that resolves to give a GuildMember object. - */ -export type BushGuildMemberResolvable = GuildMemberResolvable | BushGuildMember | BushUserResolvable; - -/** - * Data that can be resolved to a Role object. - */ -export type BushRoleResolvable = RoleResolvable | BushRole | Snowflake; - -/** - * Data that can be resolved to a Message object. - */ -export type BushMessageResolvable = MessageResolvable | BushMessage | Snowflake; - -/** - * Data that can be resolved into a GuildEmoji object. - */ -export type BushEmojiResolvable = EmojiResolvable | Snowflake | BushGuildEmoji | BushReactionEmoji; - -/** - * Data that can be resolved to give an emoji identifier. This can be: - * * The unicode representation of an emoji - * * The ``, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji - * * An EmojiResolvable - */ -export type BushEmojiIdentifierResolvable = EmojiIdentifierResolvable | string | BushEmojiResolvable; - -/** - * Data that can be resolved to a Thread Channel object. - */ -export type BushThreadChannelResolvable = ThreadChannelResolvable | BushThreadChannel | Snowflake; - -/** - * Data that resolves to give an ApplicationCommand object. - */ -export type BushApplicationCommandResolvable = ApplicationCommandResolvable | BushApplicationCommand | Snowflake; - -/** - * Data that can be resolved to a GuildTextChannel object. - */ -export type BushGuildTextChannelResolvable = GuildTextChannelResolvable | BushTextChannel | BushNewsChannel | Snowflake; - -/** - * Data that can be resolved to give a Channel object. - */ -export type BushChannelResolvable = ChannelResolvable | BushAnyChannel | Snowflake; - -/** - * Data that can be resolved to give a Guild Channel object. - */ -export type BushGuildChannelResolvable = GuildChannelResolvable | Snowflake | BushGuildBasedChannel; - -export type BushAnyChannel = - | BushCategoryChannel - | BushDMChannel - | PartialBushDMChannel - | PartialGroupDMChannel - | BushNewsChannel - | BushStageChannel - | BushTextChannel - | BushThreadChannel - | BushVoiceChannel; - -/** - * The channels that are text-based. - */ -export type BushTextBasedChannel = - | BushDMChannel - | PartialBushDMChannel - | BushNewsChannel - | BushTextChannel - | BushThreadChannel - | BushVoiceChannel; - -/** - * The types of channels that are text-based. - */ -export type BushTextBasedChannelTypes = BushTextBasedChannel['type']; - -export type BushVoiceBasedChannel = Extract; - -export type BushGuildBasedChannel = Extract; - -export type BushNonCategoryGuildBasedChannel = Exclude; - -export type BushNonThreadGuildBasedChannel = Exclude; - -export type BushGuildTextBasedChannel = Extract; - -/** - * Data that can be resolved to a Text Channel object. - */ -export type BushTextChannelResolvable = Snowflake | BushTextChannel; - -/** - * Data that can be resolved to a GuildVoiceChannel object. - */ -export type BushGuildVoiceChannelResolvable = BushVoiceBasedChannel | Snowflake; - -export interface BushMappedChannelCategoryTypes { - [ChannelType.GuildNews]: BushNewsChannel; - [ChannelType.GuildVoice]: BushVoiceChannel; - [ChannelType.GuildText]: BushTextChannel; - [ChannelType.GuildStageVoice]: BushStageChannel; - [ChannelType.GuildForum]: never; // TODO: Fix when guild forums come out -} - -export type BushMappedGuildChannelTypes = { - [ChannelType.GuildCategory]: BushCategoryChannel; -} & BushMappedChannelCategoryTypes; - -/** - * The data returned from a thread fetch that returns multiple threads. - */ -export interface BushFetchedThreads extends FetchedThreads { - /** - * The threads that were fetched, with any members returned - */ - threads: Collection; - - /** - * Whether there are potentially additional threads that require a subsequent call - */ - hasMore?: boolean; -} - -export type BushGuildCacheMessage = CacheTypeReducer< - Cached, - BushMessage, - APIMessage, - BushMessage | APIMessage, - BushMessage | APIMessage ->; - -export { ApplicationCommandOptionType as SlashType } from 'discord.js'; -- cgit