diff options
Diffstat (limited to 'src/lib/extensions')
51 files changed, 1636 insertions, 303 deletions
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index a9e172a..d7c8b60 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -1,26 +1,25 @@ import type { BushApplicationCommand, BushBaseGuildEmojiManager, - BushChannel, BushChannelManager, BushClientEvents, BushClientUser, BushGuildManager, BushReactionEmoji, + BushStageChannel, BushUserManager, Config } from '#lib'; -import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; +import { patch, PatchedElements } from '@notenoughupdates/events-intercept'; import * as Sentry from '@sentry/node'; import { AkairoClient, ContextMenuCommandHandler, version as akairoVersion } from 'discord-akairo'; import { + Awaitable, Intents, Options, Structures, version as discordJsVersion, - type Awaitable, type Collection, - type DMChannel, type InteractionReplyOptions, type Message, type MessageEditOptions, @@ -31,6 +30,7 @@ import { type Snowflake, type WebhookEditMessageOptions } from 'discord.js'; +import EventEmitter from 'events'; import path from 'path'; import readline from 'readline'; import type { Sequelize as SequelizeType } from 'sequelize'; @@ -100,9 +100,28 @@ export type BushEmojiIdentifierResolvable = string | BushEmojiResolvable; export type BushThreadChannelResolvable = BushThreadChannel | Snowflake; export type BushApplicationCommandResolvable = BushApplicationCommand | Snowflake; export type BushGuildTextChannelResolvable = BushTextChannel | BushNewsChannel | Snowflake; -export type BushChannelResolvable = BushChannel | Snowflake; -export type BushTextBasedChannels = PartialDMChannel | BushDMChannel | BushTextChannel | BushNewsChannel | BushThreadChannel; -export type BushGuildTextBasedChannel = Exclude<BushTextBasedChannels, PartialDMChannel | BushDMChannel | DMChannel>; +export type BushChannelResolvable = BushAnyChannel | Snowflake; +export type BushGuildChannelResolvable = Snowflake | BushGuildBasedChannel; +export type BushAnyChannel = + | BushCategoryChannel + | BushDMChannel + | PartialDMChannel + | BushNewsChannel + | BushStageChannel + // eslint-disable-next-line deprecation/deprecation + | BushStoreChannel + | BushTextChannel + | BushThreadChannel + | BushVoiceChannel; +export type BushTextBasedChannel = PartialDMChannel | BushThreadChannel | BushDMChannel | BushNewsChannel | BushTextChannel; +export type BushTextBasedChannelTypes = BushTextBasedChannel['type']; +export type BushVoiceBasedChannel = Extract<BushAnyChannel, { bitrate: number }>; +export type BushGuildBasedChannel = Extract<BushAnyChannel, { guild: BushGuild }>; +export type BushNonThreadGuildBasedChannel = Exclude<BushGuildBasedChannel, BushThreadChannel>; +export type BushGuildTextBasedChannel = Extract<BushGuildBasedChannel, BushTextBasedChannel>; +export type BushTextChannelResolvable = Snowflake | BushTextChannel; +export type BushGuildVoiceChannelResolvable = BushVoiceBasedChannel | Snowflake; + export interface BushFetchedThreads { threads: Collection<Snowflake, BushThreadChannel>; hasMore?: boolean; @@ -118,29 +137,86 @@ type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? const __dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * The main hub for interacting with the Discord API. + */ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Ready> { public declare channels: BushChannelManager; public declare readonly emojis: BushBaseGuildEmojiManager; public declare guilds: BushGuildManager; public declare user: If<Ready, BushClientUser>; public declare users: BushUserManager; + public declare util: BushClientUtil; + public declare ownerID: Snowflake[]; + /** + * Whether or not the client is ready. + */ public customReady = false; - public stats: { cpu: number | undefined; commandsUsed: bigint } = { cpu: undefined, commandsUsed: 0n }; + + /** + * Stats for the client. + */ + public stats: BushStats = { cpu: undefined, commandsUsed: 0n }; + + /** + * The configuration for the client. + */ public config: Config; + + /** + * The handler for the bot's listeners. + */ public listenerHandler: BushListenerHandler; + + /** + * The handler for the bot's command inhibitors. + */ public inhibitorHandler: BushInhibitorHandler; + + /** + * The handler for the bot's commands. + */ public commandHandler: BushCommandHandler; + + /** + * The handler for the bot's tasks. + */ public taskHandler: BushTaskHandler; + + /** + * The handler for the bot's context menu commands. + */ public contextMenuCommandHandler: ContextMenuCommandHandler; - public declare util: BushClientUtil; - public declare ownerID: Snowflake[]; + + /** + * The database connection for the bot. + */ public db: SequelizeType; + + /** + * A custom logging system for the bot. + */ public logger = BushLogger; + + /** + * Constants for the bot. + */ public constants = BushConstants; + + /** + * Cached global and guild database data. + */ public cache = new BushCache(); + + /** + * Sentry error reporting for the bot. + */ public sentry!: typeof Sentry; + /** + * @param config The configuration for the bot. + */ public constructor(config: Config) { super({ ownerID: config.owners, @@ -163,25 +239,18 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re this.token = config.token as If<Ready, string, string | null>; this.config = config; - // Create listener handler this.listenerHandler = new BushListenerHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'listeners'), automateCategories: true }); - - // Create inhibitor handler this.inhibitorHandler = new BushInhibitorHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'inhibitors'), automateCategories: true }); - - // Create task handler this.taskHandler = new BushTaskHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'tasks'), automateCategories: true }); - - // Create command handler this.commandHandler = new BushCommandHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'commands'), prefix: async ({ guild }: Message) => { @@ -215,12 +284,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re useSlashPermissions: true, aliasReplacement: /-/g }); - this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, { directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'), automateCategories: true }); - this.util = new BushClientUtil(this); this.db = new Sequelize({ database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot', @@ -234,15 +301,24 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re }); } - get console(): typeof BushLogger { + /** + * A custom logging system for the bot. + */ + public get console(): typeof BushLogger { return this.logger; } - get consts(): typeof BushConstants { + /** + * Constants for the bot. + */ + public get consts(): typeof BushConstants { return this.constants; } - public static init(): void { + /** + * Extends discord.js structures before the client is instantiated. + */ + public static extendStructures(): void { Structures.extend('GuildEmoji', () => BushGuildEmoji); Structures.extend('DMChannel', () => BushDMChannel); Structures.extend('TextChannel', () => BushTextChannel); @@ -265,18 +341,28 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re Structures.extend('SelectMenuInteraction', () => BushSelectMenuInteraction); } - // Initialize everything - async #init() { - this.commandHandler.useListenerHandler(this.listenerHandler); + /** + * Initializes the bot. + */ + async init() { + if (!process.version.startsWith('v17.')) { + void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false)); + process.exit(2); + } + this.commandHandler.useInhibitorHandler(this.inhibitorHandler); + this.commandHandler.useListenerHandler(this.listenerHandler); + this.commandHandler.useTaskHandler(this.taskHandler); + this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler); this.commandHandler.ignorePermissions = this.config.owners; this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.global.superUsers])]; this.listenerHandler.setEmitters({ client: this, commandHandler: this.commandHandler, - listenerHandler: this.listenerHandler, inhibitorHandler: this.inhibitorHandler, + listenerHandler: this.listenerHandler, taskHandler: this.taskHandler, + contextMenuCommandHandler: this.contextMenuCommandHandler, process, stdin: rl, gateway: this.ws @@ -301,28 +387,31 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re void this.logger.success('startup', `Successfully connected to <<Sentry>>.`, false); // loads all the handlers - const loaders = { + const handlers = { commands: this.commandHandler, - contextMenuCommand: this.contextMenuCommandHandler, + contextMenuCommands: this.contextMenuCommandHandler, listeners: this.listenerHandler, inhibitors: this.inhibitorHandler, tasks: this.taskHandler }; - for (const loader in loaders) { - try { - await loaders[loader as keyof typeof loaders].loadAll(); - void this.logger.success('startup', `Successfully loaded <<${loader}>>.`, false); - } catch (e) { - void this.logger.error('startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack || e}`, false); - } - } - await this.dbPreInit(); - await UpdateCacheTask.init(this); - void this.console.success('startup', `Successfully created <<cache>>.`, false); - this.stats.commandsUsed = await UpdateStatsTask.init(); + const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) => + handler + .loadAll() + .then(() => { + void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false); + }) + .catch((e) => { + void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${e?.stack || e}`, false); + if (process.argv.includes('dry')) process.exit(1); + }) + ); + await Promise.allSettled(handlerPromises); } - public async dbPreInit() { + /** + * Connects to the database, initializes models, and creates tables if they do not exist. + */ + private async dbPreInit() { try { await this.db.authenticate(); Global.initModel(this.db); @@ -348,10 +437,6 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re * Starts the bot */ public async start() { - if (!process.version.startsWith('v17.')) { - void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false)); - process.exit(2); - } this.intercept('ready', async (arg, done) => { await this.guilds.fetch(); const promises = this.guilds.cache.map((guild) => { @@ -368,7 +453,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re global.util = this.util; try { - await this.#init(); + await this.dbPreInit(); + await UpdateCacheTask.init(this); + void this.console.success('startup', `Successfully created <<cache>>.`, false); + this.stats.commandsUsed = await UpdateStatsTask.init(); await this.login(this.token!); } catch (e) { await this.console.error('start', util.inspect(e, { colors: true, depth: 1 }), false); @@ -389,13 +477,14 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re public override isOwner(user: BushUserResolvable): boolean { return this.config.owners.includes(this.users.resolveId(user!)!); } + public override isSuperUser(user: BushUserResolvable): boolean { const userID = this.users.resolveId(user)!; return !!client.cache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID); } } -export interface BushClient extends PatchedElements { +export interface BushClient extends EventEmitter, PatchedElements { on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this; on<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; @@ -411,3 +500,15 @@ export interface BushClient extends PatchedElements { removeAllListeners<K extends keyof BushClientEvents>(event?: K): this; removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): this; } + +export interface BushStats { + /** + * The average cpu usage of the bot from the past 60 seconds. + */ + cpu: number | undefined; + + /** + * The total number of times any command has been used. + */ + commandsUsed: bigint; +} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index ab1f3ed..5ae2ac0 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -2,6 +2,7 @@ import { Arg, BushConstants, Global, + GlobalCache, type BushClient, type BushInspectOptions, type BushMessage, @@ -438,6 +439,12 @@ export class BushClientUtil extends ClientUtil { return array.join(', '); } + public getGlobal(): GlobalCache; + public getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K]; + public getGlobal(key?: keyof GlobalCache) { + return key ? client.cache.global[key] : client.cache.global; + } + /** * Add or remove an element from an array stored in the Globals database. * @param action Either `add` or `remove` an element. @@ -610,11 +617,11 @@ export class BushClientUtil extends ClientUtil { /** * Wait an amount in seconds. - * @param s The number of seconds to wait + * @param seconds The number of seconds to wait * @returns A promise that resolves after the specified amount of seconds */ - public async sleep(s: number) { - return new Promise((resolve) => setTimeout(resolve, s * 1000)); + public async sleep(seconds: number) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } /** @@ -629,8 +636,13 @@ export class BushClientUtil extends ClientUtil { }); } + /** + * Fetches a user from discord. + * @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<BushUser | undefined> { - if (!user) return undefined; + if (user == null) return undefined; const id = user instanceof User || user instanceof GuildMember || user instanceof ThreadMember ? user.id @@ -643,6 +655,11 @@ export class BushClientUtil extends ClientUtil { else return await client.users.fetch(id).catch(() => undefined); } + /** + * Get the pronouns of a discord user from pronoundb.org + * @param user The user to retrieve the promises of. + * @returns The human readable pronouns of the user, or undefined if they do not have any. + */ public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> { const _user = await this.resolveNonCachedUser(user); if (!_user) throw new Error(`Cannot find user ${user}`); @@ -657,6 +674,11 @@ export class BushClientUtil extends ClientUtil { return client.constants.pronounMapping[apiRes.pronouns!]!; } + /** + * List the methods of an object. + * @param obj The object to get the methods of. + * @returns A string with each method on a new line. + */ public getMethods(obj: Record<string, any>): string { // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class // answer by Bruno Grieder @@ -700,13 +722,17 @@ export class BushClientUtil extends ClientUtil { return props.join('\n'); } + /** + * Uploads an image to imgur. + * @param image The image to upload. + * @returns The url of the imgur. + */ public async uploadImageToImgur(image: string) { const clientId = this.client.config.credentials.imgurClientId; const resp = (await got .post('https://api.imgur.com/3/upload', { headers: { - // Authorization: `Bearer ${token}`, Authorization: `Client-ID ${clientId}`, Accept: 'application/json' }, @@ -721,18 +747,38 @@ export class BushClientUtil extends ClientUtil { return resp.data.link; } + /** + * Checks if a user has a certain guild permission (doesn't check channel permissions). + * @param message The message to check the user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { const missing = message.member?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; } + /** + * Check if the client has certain permissions in the guild (doesn't check channel permissions). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @returns The missing permissions or null if none are missing. + */ public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) { const missing = message.guild?.me?.permissions.missing(permissions) ?? []; return missing.length ? missing : null; } + /** + * Check if the client has permission to send messages in the channel as well as check if they have other permissions + * in the guild (or the channel if `checkChannel` is `true`). + * @param message The message to check the client user from. + * @param permissions The permissions to check for. + * @param checkChannel Whether to check the channel permissions instead of the guild permissions. + * @returns The missing permissions or null if none are missing. + */ public clientSendAndPermCheck( message: BushMessage | BushSlashMessage, permissions: PermissionResolvable = [], @@ -752,6 +798,11 @@ export class BushClientUtil extends ClientUtil { return missing.length ? missing : null; } + /** + * Gets the prefix based off of the message. + * @param message The message to get the prefix from. + * @returns The prefix. + */ public prefix(message: BushMessage | BushSlashMessage): string { return message.util.isSlash ? '/' @@ -760,14 +811,55 @@ export class BushClientUtil extends ClientUtil { : message.util.parsed?.prefix ?? client.config.prefix; } + /** + * Recursively apply provided options operations on object + * and all of the object properties that are either object or function. + * + * By default freezes object. + * + * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions` + * @param options default `{ action: 'freeze' }` + * @param options.action + * ``` + * | action | Add | Modify | Delete | Reconfigure | + * | ----------------- | --- | ------ | ------ | ----------- | + * | preventExtensions | - | + | + | + | + * | seal | - | + | - | - | + * | freeze | - | - | - | - | + * ``` + * + * @returns Initial object with applied options action + */ public get deepFreeze() { return deepLock; } + /** + * Recursively apply provided options operations on object + * and all of the object properties that are either object or function. + * + * By default freezes object. + * + * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions` + * @param options default `{ action: 'freeze' }` + * @param options.action + * ``` + * | action | Add | Modify | Delete | Reconfigure | + * | ----------------- | --- | ------ | ------ | ----------- | + * | preventExtensions | - | + | + | + | + * | seal | - | + | - | - | + * | freeze | - | - | - | - | + * ``` + * + * @returns Initial object with applied options action + */ public static get deepFreeze() { return deepLock; } + /** + * A wrapper for the Argument class that adds custom typings. + */ public get arg() { return Arg; } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index ae3dcb2..6b54e20 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -174,7 +174,7 @@ export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { export type BushMissingPermissionSupplier = (message: BushMessage | BushSlashMessage) => Promise<any> | any; -export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'> { +interface ExtendedCommandOptions { /** * Whether the command is hidden from the help command. */ @@ -191,11 +191,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis restrictedGuilds?: Snowflake[]; /** - * The description of the command. - */ - description: string; - - /** * Show how to use the command. */ usage: string[]; @@ -206,13 +201,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis examples: string[]; /** - * The arguments for the command. - */ - args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; - - category: string; - - /** * A fake command, completely hidden from the help command. */ pseudo?: boolean; @@ -223,6 +211,27 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis bypassChannelBlacklist?: boolean; /** + * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions + */ + helpArgs?: BushArgumentOptions[]; +} + +export interface BaseBushCommandOptions + extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'>, + ExtendedCommandOptions { + /** + * The description of the command. + */ + description: string; + + /** + * The arguments for the command. + */ + args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; + + category: string; + + /** * Permissions required by the client to run this command. */ clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; @@ -241,11 +250,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis * Restrict this argument to super users. */ superUserOnly?: boolean; - - /** - * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions - */ - helpArgs?: BushArgumentOptions[]; } export type BushCommandOptions = Omit<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>; @@ -329,90 +333,67 @@ export class BushCommand extends Command { }); } - const newOptions: CommandOptions = {}; - if ('aliases' in options_) newOptions.aliases = options_.aliases; - if ('args' in options_ && typeof options_.args === 'object') { - const newTextArgs: ArgumentOptions[] = []; - const newSlashArgs: SlashOption[] = []; - for (const arg of options_.args) { - if (arg.only !== 'slash' && !options_.slashOnly) { - const newArg: ArgumentOptions = {}; - if ('default' in arg) newArg.default = arg.default; - if ('description' in arg) newArg.description = arg.description; - if ('flag' in arg) newArg.flag = arg.flag; - if ('id' in arg) newArg.id = arg.id; - if ('index' in arg) newArg.index = arg.index; - if ('limit' in arg) newArg.limit = arg.limit; - if ('match' in arg) newArg.match = arg.match; - if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; - if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; - if ('otherwise' in arg) newArg.otherwise = arg.otherwise; - if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { - newArg.prompt = {}; - if ('prompt' in arg) newArg.prompt.start = arg.prompt; - if ('retry' in arg) newArg.prompt.retry = arg.retry; - if ('optional' in arg) newArg.prompt.optional = arg.optional; + const newOptions: Partial<CommandOptions & ExtendedCommandOptions> = {}; + for (const _key in options_) { + const key = _key as keyof typeof options_; // you got to love typescript + if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') { + const newTextArgs: ArgumentOptions[] = []; + const newSlashArgs: SlashOption[] = []; + for (const arg of options_.args) { + if (arg.only !== 'slash' && !options_.slashOnly) { + const newArg: ArgumentOptions = {}; + if ('default' in arg) newArg.default = arg.default; + if ('description' in arg) newArg.description = arg.description; + if ('flag' in arg) newArg.flag = arg.flag; + if ('id' in arg) newArg.id = arg.id; + if ('index' in arg) newArg.index = arg.index; + if ('limit' in arg) newArg.limit = arg.limit; + if ('match' in arg) newArg.match = arg.match; + if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise; + if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags; + if ('otherwise' in arg) newArg.otherwise = arg.otherwise; + if ('prompt' in arg || 'retry' in arg || 'optional' in arg) { + newArg.prompt = {}; + if ('prompt' in arg) newArg.prompt.start = arg.prompt; + if ('retry' in arg) newArg.prompt.retry = arg.retry; + if ('optional' in arg) newArg.prompt.optional = arg.optional; + } + if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; + if ('unordered' in arg) newArg.unordered = arg.unordered; + newTextArgs.push(newArg); + } + if ( + arg.only !== 'text' && + !('slashOptions' in options_) && + (options_.slash || options_.slashOnly) && + arg.slashType !== false + ) { + const newArg: { + [key in SlashOptionKeys]?: any; + } = { + name: arg.id, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + description: arg.prompt || arg.description || 'No description provided.', + type: arg.slashType + }; + if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; + if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; + if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; + if ('choices' in arg) newArg.choices = arg.choices; + if ('minValue' in arg) newArg.minValue = arg.minValue; + if ('maxValue' in arg) newArg.maxValue = arg.maxValue; + newArg.required = 'optional' in arg ? !arg.optional : true; + newSlashArgs.push(newArg as SlashOption); } - if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster; - if ('unordered' in arg) newArg.unordered = arg.unordered; - newTextArgs.push(newArg); - } - if ( - arg.only !== 'text' && - !('slashOptions' in options_) && - (options_.slash || options_.slashOnly) && - arg.slashType !== false - ) { - const newArg: { - [key in SlashOptionKeys]?: any; - } = { - name: arg.id, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - description: arg.prompt || arg.description || 'No description provided.', - type: arg.slashType - }; - if ('slashResolve' in arg) newArg.resolve = arg.slashResolve; - if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete; - if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes; - if ('choices' in arg) newArg.choices = arg.choices; - if ('minValue' in arg) newArg.minValue = arg.minValue; - if ('maxValue' in arg) newArg.maxValue = arg.maxValue; - newArg.required = 'optional' in arg ? !arg.optional : true; - newSlashArgs.push(newArg as SlashOption); } + if (newTextArgs.length > 0) newOptions.args = newTextArgs; + if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; + } else if (key === 'clientPermissions' || key === 'userPermissions') { + newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; + } else { + newOptions[key] = options_[key]; } - if (newTextArgs.length > 0) newOptions.args = newTextArgs; - if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; } - type perm = PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; - - if ('argumentDefaults' in options_) newOptions.argumentDefaults = options_.argumentDefaults; - if ('before' in options_) newOptions.before = options_.before; - if ('channel' in options_) newOptions.channel = options_.channel; - if ('clientPermissions' in options_) newOptions.clientPermissions = options_.clientPermissions as perm; - if ('condition' in options_) newOptions.condition = options_.condition; - if ('cooldown' in options_) newOptions.cooldown = options_.cooldown; - if ('description' in options_) newOptions.description = options_.description; - if ('editable' in options_) newOptions.editable = options_.editable; - if ('flags' in options_) newOptions.flags = options_.flags; - if ('ignoreCooldown' in options_) newOptions.ignoreCooldown = options_.ignoreCooldown; - if ('ignorePermissions' in options_) newOptions.ignorePermissions = options_.ignorePermissions; - if ('lock' in options_) newOptions.lock = options_.lock; - if ('onlyNsfw' in options_) newOptions.onlyNsfw = options_.onlyNsfw; - if ('optionFlags' in options_) newOptions.optionFlags = options_.optionFlags; - if ('ownerOnly' in options_) newOptions.ownerOnly = options_.ownerOnly; - if ('prefix' in options_) newOptions.prefix = options_.prefix; - if ('quoted' in options_) newOptions.quoted = options_.quoted; - if ('ratelimit' in options_) newOptions.ratelimit = options_.ratelimit; - if ('regex' in options_) newOptions.regex = options_.regex; - if ('separator' in options_) newOptions.separator = options_.separator; - if ('slash' in options_) newOptions.slash = options_.slash; - if ('slashEphemeral' in options_) newOptions.slashEphemeral = options_.slashEphemeral; - if ('slashGuilds' in options_) newOptions.slashGuilds = options_.slashGuilds; - if ('slashOptions' in options_) newOptions.slashOptions = options_.slashOptions; - if ('superUserOnly' in options_) newOptions.superUserOnly = options_.superUserOnly; - if ('typing' in options_) newOptions.typing = options_.typing; - if ('userPermissions' in options_) newOptions.userPermissions = options_.userPermissions as perm; super(id, newOptions); @@ -447,9 +428,14 @@ export class BushCommand extends Command { } } -export interface BushCommand { - exec(message: BushMessage, args: any): any; - exec(message: BushMessage | BushSlashMessage, args: any): any; +export interface BushCommand extends Command { + /** + * Executes the command. + * @param message - Message that triggered the command. + * @param args - Evaluated arguments. + */ + exec<R, A>(message: BushMessage, args: A): R; + exec<R, A>(message: BushMessage | BushSlashMessage, args: A): R; } type SlashOptionKeys = diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts index c3f9994..7f13594 100644 --- a/src/lib/extensions/discord-akairo/BushInhibitor.ts +++ b/src/lib/extensions/discord-akairo/BushInhibitor.ts @@ -6,6 +6,13 @@ export class BushInhibitor extends Inhibitor { } 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. + * @param message - Message being handled. + * @param command - Command to check. + */ exec(message: BushMessage, command: BushCommand): any; exec(message: BushMessage | BushSlashMessage, command: BushCommand): any; } diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts index c0a4a60..448963b 100644 --- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts +++ b/src/lib/extensions/discord-akairo/BushSlashMessage.ts @@ -12,6 +12,6 @@ export class BushSlashMessage extends AkairoMessage { } } -export interface BushSlashMessage { +export interface BushSlashMessage extends AkairoMessage { get guild(): BushGuild | null; } diff --git a/src/lib/extensions/discord.js/BushActivity.ts b/src/lib/extensions/discord.js/BushActivity.ts index e438918..3f19232 100644 --- a/src/lib/extensions/discord.js/BushActivity.ts +++ b/src/lib/extensions/discord.js/BushActivity.ts @@ -2,6 +2,9 @@ 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; diff --git a/src/lib/extensions/discord.js/BushApplicationCommand.ts b/src/lib/extensions/discord.js/BushApplicationCommand.ts index 35d598d..8298830 100644 --- a/src/lib/extensions/discord.js/BushApplicationCommand.ts +++ b/src/lib/extensions/discord.js/BushApplicationCommand.ts @@ -3,6 +3,9 @@ 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<PermissionsFetchType = {}> extends ApplicationCommand<PermissionsFetchType> { public declare readonly client: BushClient; public declare guild: BushGuild | null; diff --git a/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts b/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts index 7947acd..2aa366d 100644 --- a/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts +++ b/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts @@ -14,12 +14,19 @@ import { type Snowflake } from 'discord.js'; +/** + * Manages API methods for application commands and stores their cache. + */ export class BushApplicationCommandManager< ApplicationCommandScope = BushApplicationCommand<{ guild: BushGuildResolvable }>, PermissionsOptionsExtras = { guild: BushGuildResolvable }, PermissionsGuildType = null > extends CachedManager<Snowflake, ApplicationCommandScope, BushApplicationCommandResolvable> { public constructor(client: BushClient, iterable?: Iterable<unknown>); + + /** + * The manager for permissions of arbitrary commands on arbitrary guilds + */ public permissions: BushApplicationCommandPermissionsManager< { command?: BushApplicationCommandResolvable } & PermissionsOptionsExtras, { command: BushApplicationCommandResolvable } & PermissionsOptionsExtras, @@ -27,22 +34,112 @@ export class BushApplicationCommandManager< 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; - public create(command: ApplicationCommandData): Promise<ApplicationCommandScope>; - public create(command: ApplicationCommandData, guildId: Snowflake): Promise<BushApplicationCommand>; + + /** + * 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<ApplicationCommandScope>; + + /** + * 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<ApplicationCommandScope | null>; + + /** + * 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<ApplicationCommandScope>; public edit( command: BushApplicationCommandResolvable, data: ApplicationCommandData, guildId: Snowflake ): Promise<BushApplicationCommand>; + + /** + * 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<BushApplicationCommand>; public fetch(options: FetchApplicationCommandOptions): Promise<Collection<string, ApplicationCommandScope>>; public fetch(id: Snowflake, options?: FetchApplicationCommandOptions): Promise<ApplicationCommandScope>; public fetch(id?: Snowflake, options?: FetchApplicationCommandOptions): Promise<Collection<Snowflake, ApplicationCommandScope>>; + + /** + * 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<Collection<Snowflake, ApplicationCommandScope>>; public set(commands: ApplicationCommandData[], guildId: Snowflake): Promise<Collection<Snowflake, BushApplicationCommand>>; + + /** + * 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<APIApplicationCommand, 'id' | 'application_id' | 'guild_id'>; diff --git a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts b/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts index 5f2e4da..ff32be4 100644 --- a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts +++ b/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts @@ -13,6 +13,9 @@ import { } from 'discord.js'; import type { ApplicationCommandPermissionTypes } from 'discord.js/typings/enums'; +/** + * Manages API methods for permissions of Application Commands. + */ export class BushApplicationCommandPermissionsManager< BaseOptions, FetchSingleOptions, @@ -21,18 +24,95 @@ export class BushApplicationCommandPermissionsManager< 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<ApplicationCommandPermissions[]>; + + /** + * 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<boolean>; + + /** + * 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<ApplicationCommandPermissions[]>; public fetch(options: BaseOptions): Promise<Collection<Snowflake, ApplicationCommandPermissions[]>>; + + /** + * 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 & { @@ -44,6 +124,37 @@ export class BushApplicationCommandPermissionsManager< roles: BushRoleResolvable | BushRoleResolvable[]; }) ): Promise<ApplicationCommandPermissions[]>; + + /** + * 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<ApplicationCommandPermissions[]>; @@ -52,7 +163,19 @@ export class BushApplicationCommandPermissionsManager< fullPermissions: GuildApplicationCommandPermissionData[]; } ): Promise<Collection<Snowflake, ApplicationCommandPermissions[]>>; + + /** + * 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 diff --git a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts b/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts index 7c913a9..347ff65 100644 --- a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts +++ b/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts @@ -2,7 +2,15 @@ import type { BushClient, BushEmojiIdentifierResolvable, BushEmojiResolvable, Bu import { CachedManager, type Snowflake } from 'discord.js'; import { type RawGuildEmojiData } from 'discord.js/typings/rawDataTypes'; +/** + * Holds methods to resolve GuildEmojis and stores their cache. + */ export class BushBaseGuildEmojiManager extends CachedManager<Snowflake, BushGuildEmoji, BushEmojiResolvable> { public constructor(client: BushClient, iterable?: Iterable<RawGuildEmojiData>); + + /** + * 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 index 742ea76..9b260dc 100644 --- a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts +++ b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts @@ -8,6 +8,9 @@ import { } 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<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>; diff --git a/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts b/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts new file mode 100644 index 0000000..21be206 --- /dev/null +++ b/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts @@ -0,0 +1,13 @@ +import { BaseGuildVoiceChannel, Collection, Snowflake } from 'discord.js'; +import { BushCategoryChannel } from './BushCategoryChannel'; +import { BushGuild } from './BushGuild'; +import { BushGuildMember } from './BushGuildMember'; + +/** + * Represents a voice-based guild channel on Discord. + */ +export class BushBaseGuildVoiceChannel extends BaseGuildVoiceChannel { + public readonly members: Collection<Snowflake, BushGuildMember>; + public guild: BushGuild; + public readonly parent: BushCategoryChannel | null; +} diff --git a/src/lib/extensions/discord.js/BushButtonInteraction.ts b/src/lib/extensions/discord.js/BushButtonInteraction.ts index 18d6b35..2bc1c25 100644 --- a/src/lib/extensions/discord.js/BushButtonInteraction.ts +++ b/src/lib/extensions/discord.js/BushButtonInteraction.ts @@ -1,15 +1,18 @@ -import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannels, BushUser } from '#lib'; +import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib'; import type { APIInteractionGuildMember } from 'discord-api-types/v9'; import { ButtonInteraction, type CacheType, type CacheTypeReducer } from 'discord.js'; import type { RawMessageButtonInteractionData } from 'discord.js/typings/rawDataTypes'; +/** + * Represents a button interaction. + */ export class BushButtonInteraction<Cached extends CacheType = CacheType> extends ButtonInteraction<Cached> { public declare readonly channel: CacheTypeReducer< Cached, BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, - BushTextBasedChannels | null + BushTextBasedChannel | null >; public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>; public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>; diff --git a/src/lib/extensions/discord.js/BushCategoryChannel.ts b/src/lib/extensions/discord.js/BushCategoryChannel.ts index c30a761..b711a54 100644 --- a/src/lib/extensions/discord.js/BushCategoryChannel.ts +++ b/src/lib/extensions/discord.js/BushCategoryChannel.ts @@ -1,10 +1,13 @@ -import { type BushClient, type BushGuild, type BushGuildChannel, type BushGuildMember } from '#lib'; +import { BushNonThreadGuildBasedChannel, type BushClient, type BushGuild, type BushGuildMember } from '#lib'; import { CategoryChannel, type Collection, type Snowflake } 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 readonly children: Collection<Snowflake, BushGuildChannel>; + public declare readonly children: Collection<Snowflake, Exclude<BushNonThreadGuildBasedChannel, BushCategoryChannel>>; public declare guild: BushGuild; public declare readonly members: Collection<Snowflake, BushGuildMember>; public declare readonly parent: CategoryChannel | null; diff --git a/src/lib/extensions/discord.js/BushChannel.d.ts b/src/lib/extensions/discord.js/BushChannel.d.ts index 9a78b9a..42443ba 100644 --- a/src/lib/extensions/discord.js/BushChannel.d.ts +++ b/src/lib/extensions/discord.js/BushChannel.d.ts @@ -1,8 +1,12 @@ -import type { BushClient, BushStageChannel, BushTextBasedChannels, BushThreadChannel, BushVoiceChannel } from '#lib'; +import type { BushClient, BushTextBasedChannel, BushThreadChannel } from '#lib'; import { Channel, type ChannelMention, type Snowflake } from 'discord.js'; import type { ChannelTypes } from 'discord.js/typings/enums'; import type { RawChannelData } from 'discord.js/typings/rawDataTypes'; +import { BushBaseGuildVoiceChannel } from './BushBaseGuildVoiceChannel'; +/** + * Represents any channel on Discord. + */ export class BushChannel extends Channel { public constructor(client: BushClient, data?: RawChannelData, immediatePatch?: boolean); public readonly createdAt: Date; @@ -11,10 +15,10 @@ export class BushChannel extends Channel { public id: Snowflake; public readonly partial: false; public type: keyof typeof ChannelTypes; - public delete(): Promise<BushChannel>; - public fetch(force?: boolean): Promise<BushChannel>; - public isText(): this is BushTextBasedChannels; - public isVoice(): this is BushVoiceChannel | BushStageChannel; + public delete(): Promise<this>; + public fetch(force?: boolean): Promise<this>; + public isText(): this is BushTextBasedChannel; + public isVoice(): this is BushBaseGuildVoiceChannel; public isThread(): this is BushThreadChannel; public toString(): ChannelMention; } diff --git a/src/lib/extensions/discord.js/BushChannelManager.d.ts b/src/lib/extensions/discord.js/BushChannelManager.d.ts index 843b956..514cdd3 100644 --- a/src/lib/extensions/discord.js/BushChannelManager.d.ts +++ b/src/lib/extensions/discord.js/BushChannelManager.d.ts @@ -1,8 +1,22 @@ -import type { BushChannel, BushChannelResolvable } from '#lib'; +import type { BushAnyChannel, BushChannelResolvable } from '#lib'; import { CachedManager, type Client, type FetchChannelOptions, type Snowflake } from 'discord.js'; import type { RawChannelData } from 'discord.js/typings/rawDataTypes'; -export class BushChannelManager extends CachedManager<Snowflake, BushChannel, BushChannelResolvable> { +/** + * A manager of channels belonging to a client + */ +export class BushChannelManager extends CachedManager<Snowflake, BushAnyChannel, BushChannelResolvable> { public constructor(client: Client, iterable: Iterable<RawChannelData>); - public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<BushChannel | null>; + + /** + * 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<BushAnyChannel | null>; } diff --git a/src/lib/extensions/discord.js/BushClientEvents.d.ts b/src/lib/extensions/discord.js/BushClientEvents.d.ts index 91096bd..10d70f9 100644 --- a/src/lib/extensions/discord.js/BushClientEvents.d.ts +++ b/src/lib/extensions/discord.js/BushClientEvents.d.ts @@ -4,16 +4,16 @@ import type { BushDMChannel, BushGuild, BushGuildBan, - BushGuildChannel, BushGuildEmoji, BushGuildMember, BushMessage, BushMessageReaction, BushNewsChannel, + BushNonThreadGuildBasedChannel, BushPresence, BushRole, BushStageInstance, - BushTextBasedChannels, + BushTextBasedChannel, BushTextChannel, BushThreadChannel, BushThreadMember, @@ -29,6 +29,7 @@ import type { import type { AkairoClientEvents } from 'discord-akairo'; import type { Collection, + GuildScheduledEvent, Interaction, InvalidRequestWarningData, Invite, @@ -45,12 +46,12 @@ export interface BushClientEvents extends AkairoClientEvents { oldCommand: BushApplicationCommand | null, newCommand: BushApplicationCommand ]; - channelCreate: [channel: BushGuildChannel]; - channelDelete: [channel: BushDMChannel | BushGuildChannel]; - channelPinsUpdate: [channel: BushTextBasedChannels, date: Date]; + channelCreate: [channel: BushNonThreadGuildBasedChannel]; + channelDelete: [channel: BushDMChannel | BushNonThreadGuildBasedChannel]; + channelPinsUpdate: [channel: BushTextBasedChannel, date: Date]; channelUpdate: [ - oldChannel: BushDMChannel | BushGuildChannel, - newChannel: BushDMChannel | BushGuildChannel + oldChannel: BushDMChannel | BushNonThreadGuildBasedChannel, + newChannel: BushDMChannel | BushNonThreadGuildBasedChannel ]; debug: [message: string]; warn: [message: string]; @@ -145,6 +146,20 @@ export interface BushClientEvents extends AkairoClientEvents { 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, diff --git a/src/lib/extensions/discord.js/BushClientUser.d.ts b/src/lib/extensions/discord.js/BushClientUser.d.ts index c307553..503413b 100644 --- a/src/lib/extensions/discord.js/BushClientUser.d.ts +++ b/src/lib/extensions/discord.js/BushClientUser.d.ts @@ -3,22 +3,96 @@ import type { Base64Resolvable, BufferResolvable, ClientPresence, + ClientUser, ClientUserEditData, PresenceData, PresenceStatusData } from 'discord.js'; import { BushUser } from './BushUser'; -export class BushClientUser extends BushUser { +/** + * Represents the logged in client's Discord user. + */ +export 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<this>; + + /** + * 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; - public setAvatar(avatar: BufferResolvable | Base64Resolvable): Promise<this>; + + /** + * 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<this>; + + /** + * 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. + * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests + * every hour. Use this sparingly!</info> + * @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<this>; } diff --git a/src/lib/extensions/discord.js/BushCommandInteraction.ts b/src/lib/extensions/discord.js/BushCommandInteraction.ts index 174fd9c..e6643f0 100644 --- a/src/lib/extensions/discord.js/BushCommandInteraction.ts +++ b/src/lib/extensions/discord.js/BushCommandInteraction.ts @@ -1,19 +1,26 @@ -import type { - BushApplicationCommand, - BushGuild, - BushGuildChannel, - BushGuildEmoji, - BushGuildMember, - BushRole, - BushUser -} from '#lib'; +import type { BushApplicationCommand, BushGuild, BushGuildEmoji, BushGuildMember, BushRole, BushUser } from '#lib'; import type { APIInteractionGuildMember } from 'discord-api-types/v9'; import { CommandInteraction, type CacheType, type CacheTypeReducer, type Invite, type Snowflake } from 'discord.js'; import type { RawCommandInteractionData } from 'discord.js/typings/rawDataTypes'; -import { BushGuildTextBasedChannel, BushTextBasedChannels, type BushClient } from '../discord-akairo/BushClient'; +import { + BushGuildTextBasedChannel, + BushNonThreadGuildBasedChannel, + BushTextBasedChannel, + type BushClient +} from '../discord-akairo/BushClient'; -export type BushGuildResolvable = BushGuild | BushGuildChannel | BushGuildMember | BushGuildEmoji | Invite | BushRole | Snowflake; +export type BushGuildResolvable = + | BushGuild + | BushNonThreadGuildBasedChannel + | BushGuildMember + | BushGuildEmoji + | Invite + | BushRole + | Snowflake; +/** + * Represents a command interaction. + */ export class BushCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> { public declare readonly client: BushClient; public declare readonly command: BushApplicationCommand | BushApplicationCommand<{ guild: BushGuildResolvable }> | null; @@ -22,7 +29,7 @@ export class BushCommandInteraction<Cached extends CacheType = CacheType> extend BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, - BushTextBasedChannels | null + BushTextBasedChannel | null >; public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>; public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>; diff --git a/src/lib/extensions/discord.js/BushDMChannel.ts b/src/lib/extensions/discord.js/BushDMChannel.ts index ca08d7e..9df9275 100644 --- a/src/lib/extensions/discord.js/BushDMChannel.ts +++ b/src/lib/extensions/discord.js/BushDMChannel.ts @@ -2,6 +2,9 @@ import type { BushClient, BushMessageManager, BushUser } from '#lib'; import { DMChannel } 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; diff --git a/src/lib/extensions/discord.js/BushEmoji.ts b/src/lib/extensions/discord.js/BushEmoji.ts index 5cdf5ac..9e42e5d 100644 --- a/src/lib/extensions/discord.js/BushEmoji.ts +++ b/src/lib/extensions/discord.js/BushEmoji.ts @@ -2,6 +2,9 @@ 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; diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 9c272ff..e12053e 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -16,6 +16,11 @@ import { Moderation } from '../../common/Moderation.js'; import { Guild as GuildDB } from '../../models/Guild.js'; import { ModLogType } from '../../models/ModLog.js'; +/** + * Represents a guild (or a server) on Discord. + * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can + * check this with {@link Guild.available}.</info> + */ export class BushGuild extends Guild { public declare readonly client: BushClient; public declare readonly me: BushGuildMember | null; @@ -97,6 +102,11 @@ export class BushGuild extends Guild { 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<BushTextChannel | undefined> { const channelId = (await this.getSetting('logChannels'))[logType]; if (!channelId) return undefined; @@ -107,14 +117,12 @@ export class BushGuild extends Guild { ); } - public async bushBan(options: { - user: BushUserResolvable | UserResolvable; - reason?: string | null; - moderator?: BushUserResolvable; - duration?: number; - deleteDays?: number; - evidence?: string; - }): Promise<'success' | 'missing permissions' | 'error banning' | 'error creating modlog entry' | 'error creating ban entry'> { + /** + * 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: BushBanOptions): Promise<BanResponse> { // checks if (!this.me!.permissions.has('BAN_MEMBERS')) return 'missing permissions'; @@ -165,18 +173,12 @@ export class BushGuild extends Guild { return ret; } - public async bushUnban(options: { - user: BushUserResolvable | BushUser; - reason?: string | null; - moderator?: BushUserResolvable; - }): Promise< - | 'success' - | 'missing permissions' - | 'user not banned' - | 'error unbanning' - | 'error creating modlog entry' - | 'error removing ban entry' - > { + /** + * 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: BushUnbanOptions): Promise<UnbanResponse> { let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; const user = (await util.resolveNonCachedUser(options.user))!; @@ -260,3 +262,70 @@ export class BushGuild extends Guild { void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] }); } } + +/** + * Options for unbanning a user + */ +interface BushUnbanOptions { + /** + * The user to unban + */ + user: BushUserResolvable | BushUser; + + /** + * The reason for unbanning the user + */ + reason?: string | null; + + /** + * The moderator who unbanned the user + */ + moderator?: BushUserResolvable; +} + +/** + * Options for banning a user + */ +interface BushBanOptions { + /** + * The user to ban + */ + user: BushUserResolvable | UserResolvable; + + /** + * 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 PunishmentResponse = 'success' | 'missing permissions' | 'error creating modlog entry'; + +/** + * Response returned when banning a user + */ +type BanResponse = PunishmentResponse | 'error banning' | 'error creating ban entry'; + +/** + * Response returned when unbanning a user + */ +type UnbanResponse = PunishmentResponse | 'user not banned' | 'error unbanning' | 'error removing ban entry'; diff --git a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts b/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts index f8e80ae..4d76b07 100644 --- a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts +++ b/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts @@ -9,15 +9,102 @@ import { 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 class BushGuildApplicationCommandManager extends BushApplicationCommandManager<BushApplicationCommand, {}, BushGuild> { public constructor(guild: BushGuild, iterable?: Iterable<RawApplicationCommandData>); public declare readonly client: BushClient; + + /** + * The guild that this manager belongs to + */ public guild: BushGuild; - public create(command: ApplicationCommandData): Promise<BushApplicationCommand>; + + /** + * 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<BushApplicationCommand>; + + /** + * 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<BushApplicationCommand | null>; + + /** + * 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<BushApplicationCommand>; + + /** + * 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<BushApplicationCommand>; public fetch(options: BaseFetchOptions): Promise<Collection<Snowflake, BushApplicationCommand>>; public fetch(id?: undefined, options?: BaseFetchOptions): Promise<Collection<Snowflake, BushApplicationCommand>>; + + /** + * 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<Collection<Snowflake, BushApplicationCommand>>; } diff --git a/src/lib/extensions/discord.js/BushGuildBan.d.ts b/src/lib/extensions/discord.js/BushGuildBan.d.ts index 4287792..11875f3 100644 --- a/src/lib/extensions/discord.js/BushGuildBan.d.ts +++ b/src/lib/extensions/discord.js/BushGuildBan.d.ts @@ -2,6 +2,9 @@ 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 class BushGuildBan extends GuildBan { public constructor(client: BushClient, data: RawGuildBanData, guild: BushGuild); public guild: BushGuild; diff --git a/src/lib/extensions/discord.js/BushGuildChannel.ts b/src/lib/extensions/discord.js/BushGuildChannel.ts index 3324dc8..6880daf 100644 --- a/src/lib/extensions/discord.js/BushGuildChannel.ts +++ b/src/lib/extensions/discord.js/BushGuildChannel.ts @@ -2,6 +2,15 @@ import type { BushClient, BushGuild } from '#lib'; import { GuildChannel } 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; diff --git a/src/lib/extensions/discord.js/BushGuildEmoji.ts b/src/lib/extensions/discord.js/BushGuildEmoji.ts index 180f78c..2c9d36b 100644 --- a/src/lib/extensions/discord.js/BushGuildEmoji.ts +++ b/src/lib/extensions/discord.js/BushGuildEmoji.ts @@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildEmojiRoleManager, BushUser } from 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; diff --git a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts b/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts index 3aae4f0..9253cad 100644 --- a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts +++ b/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts @@ -1,15 +1,51 @@ import type { BushClient, BushGuild, BushGuildEmoji, BushRole, BushRoleResolvable } from '#lib'; import { DataManager, type Collection, type Snowflake } from 'discord.js'; +/** + * Manages API methods for roles belonging to emojis and stores their cache. + */ export class BushGuildEmojiRoleManager extends DataManager<Snowflake, BushRole, BushRoleResolvable> { 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<Snowflake, BushRole> ): Promise<BushGuildEmoji>; + + /** + * 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<Snowflake, BushRole>): Promise<BushGuildEmoji>; + + /** + * 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<Snowflake, BushRole> ): Promise<BushGuildEmoji>; diff --git a/src/lib/extensions/discord.js/BushGuildManager.d.ts b/src/lib/extensions/discord.js/BushGuildManager.d.ts index 4dd0750..95719a3 100644 --- a/src/lib/extensions/discord.js/BushGuildManager.d.ts +++ b/src/lib/extensions/discord.js/BushGuildManager.d.ts @@ -10,9 +10,25 @@ import { } from 'discord.js'; import { type RawGuildData } from 'discord.js/typings/rawDataTypes'; +/** + * Manages API methods for Guilds and stores their cache. + */ export class BushGuildManager extends CachedManager<Snowflake, BushGuild, BushGuildResolvable> { public constructor(client: BushClient, iterable?: Iterable<RawGuildData>); + + /** + * Creates a guild. + * <warn>This is only available to bots in fewer than 10 guilds.</warn> + * @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<BushGuild>; + + /** + * 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<BushGuild>; public fetch(options?: FetchGuildsOptions): Promise<Collection<Snowflake, OAuth2Guild>>; } diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index fe9c4c9..f6d5259 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -1,79 +1,11 @@ -import { Moderation, ModLogType, type BushClient, type BushGuild, type BushRole, type BushUser } from '#lib'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { BushClientEvents, Moderation, ModLogType, type BushClient, type BushGuild, type BushRole, type BushUser } from '#lib'; import { GuildMember, MessageEmbed, type Partialize, type Role } from 'discord.js'; import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; -interface BushPunishmentOptions { - reason?: string | null; - moderator?: BushGuildMember; - evidence?: string; -} - -interface BushTimedPunishmentOptions extends BushPunishmentOptions { - duration?: number; -} - -interface AddRoleOptions extends BushTimedPunishmentOptions { - role: BushRole | Role; - addToModlog: boolean; -} - -interface RemoveRoleOptions extends BushTimedPunishmentOptions { - role: BushRole | Role; - addToModlog: boolean; -} - -type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm'; - -type WarnResponse = PunishmentResponse; - -type AddRoleResponse = - | PunishmentResponse - | 'user hierarchy' - | 'role managed' - | 'client hierarchy' - | 'error creating role entry' - | 'error adding role'; - -type RemoveRoleResponse = - | PunishmentResponse - | 'user hierarchy' - | 'role managed' - | 'client hierarchy' - | 'error removing role entry' - | 'error removing role'; - -type MuteResponse = - | PunishmentResponse - | 'missing permissions' - | 'no mute role' - | 'invalid mute role' - | 'mute role not manageable' - | 'error giving mute role' - | 'error creating mute entry'; - -type UnmuteResponse = - | PunishmentResponse - | 'missing permissions' - | 'no mute role' - | 'invalid mute role' - | 'mute role not manageable' - | 'error removing mute role' - | 'error removing mute entry'; - -type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking'; - -interface BushBanOptions extends BushTimedPunishmentOptions { - deleteDays?: number; -} - -type BanResponse = PunishmentResponse | 'missing permissions' | 'error creating ban entry' | 'error banning'; - -export type PartialBushGuildMember = Partialize< - BushGuildMember, - 'joinedAt' | 'joinedTimestamp', - 'warn' | 'addRole' | 'removeRole' | 'mute' | 'unmute' | 'bushKick' | 'bushBan' | 'isOwner' | 'isSuperUser' ->; - +/** + * Represents a member of a guild on Discord. + */ export class BushGuildMember extends GuildMember { public declare readonly client: BushClient; public declare guild: BushGuild; @@ -83,6 +15,14 @@ export class BushGuildMember extends GuildMember { super(client, data, guild); } + /** + * Send a punishment dm to the user. + * @param punishment The punishment that the user has received. + * @param reason The reason the user to be punished. + * @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 punishDM(punishment: string, reason?: string | null, duration?: number, sendFooter = true): Promise<boolean> { const ending = await this.guild.getSetting('punishmentEnding'); const dmEmbed = @@ -98,6 +38,12 @@ export class BushGuildMember extends GuildMember { return !!dmSuccess; } + /** + * 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 warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse | null; caseNum: number | null }> { let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; @@ -131,6 +77,12 @@ export class BushGuildMember extends GuildMember { return ret as { result: WarnResponse | null; 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} + */ public async addRole(options: AddRoleOptions): Promise<AddRoleResponse> { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; @@ -186,6 +138,12 @@ export class BushGuildMember extends GuildMember { 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 removeRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; @@ -237,11 +195,17 @@ export class BushGuildMember extends GuildMember { 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.id */) { + if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) { client.console.debug(`${this.roles.highest.position} <= ${role.position}`); return 'user hierarchy'; } else if (role.managed) { @@ -252,6 +216,12 @@ export class BushGuildMember extends GuildMember { 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 mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> { // checks if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions'; @@ -325,6 +295,12 @@ export class BushGuildMember extends GuildMember { 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 unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> { //checks if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions'; @@ -393,6 +369,12 @@ export class BushGuildMember extends GuildMember { 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<KickResponse> { // checks if (!this.guild.me?.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions'; @@ -437,6 +419,12 @@ export class BushGuildMember extends GuildMember { 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<BanResponse> { // checks if (!this.guild.me!.permissions.has('BAN_MEMBERS') || !this.bannable) return 'missing permissions'; @@ -497,11 +485,160 @@ export class BushGuildMember extends GuildMember { return ret; } - public get isOwner(): boolean { + /** + * Whether or not the user is an owner of the bot. + */ + public isOwner(): boolean { return client.isOwner(this); } - public get isSuperUser(): boolean { + /** + * Whether or not the user is a super user of the bot. + */ + public isSuperUser(): boolean { return client.isSuperUser(this); } } + +/** + * Options for punishing a user. + */ +interface BushPunishmentOptions { + /** + * The reason for the punishment. + */ + reason?: string | null; + + /** + * The moderator who punished the user. + */ + moderator?: BushGuildMember; + + /** + * Evidence for the punishment. + */ + evidence?: string; +} + +/** + * Punishment options for punishments that can be temporary. + */ +interface BushTimedPunishmentOptions extends BushPunishmentOptions { + /** + * The duration of the punishment. + */ + duration?: number; +} + +/** + * Options for a role add punishment. + */ +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. + */ +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. + */ +interface BushBanOptions extends BushTimedPunishmentOptions { + /** + * The number of days to delete the user's messages for. + */ + deleteDays?: number; +} + +type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm'; + +/** + * Response returned when warning a user. + */ +type WarnResponse = PunishmentResponse; + +/** + * Response returned when adding a role to a user. + */ +type AddRoleResponse = + | PunishmentResponse + | 'user hierarchy' + | 'role managed' + | 'client hierarchy' + | 'error creating role entry' + | 'error adding role'; + +/** + * Response returned when removing a role from a user. + */ +type RemoveRoleResponse = + | PunishmentResponse + | 'user hierarchy' + | 'role managed' + | 'client hierarchy' + | 'error removing role entry' + | 'error removing role'; + +/** + * Response returned when muting a user. + */ +type MuteResponse = + | PunishmentResponse + | 'missing permissions' + | 'no mute role' + | 'invalid mute role' + | 'mute role not manageable' + | 'error giving mute role' + | 'error creating mute entry'; + +/** + * Response returned when unmuting a user. + */ +type UnmuteResponse = + | PunishmentResponse + | 'missing permissions' + | 'no mute role' + | 'invalid mute role' + | 'mute role not manageable' + | 'error removing mute role' + | 'error removing mute entry'; + +/** + * Response returned when kicking a user. + */ +type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking'; + +/** + * Response returned when banning a user. + */ +type BanResponse = PunishmentResponse | 'missing permissions' | 'error creating ban entry' | 'error banning'; + +export type PartialBushGuildMember = Partialize< + BushGuildMember, + 'joinedAt' | 'joinedTimestamp', + 'warn' | 'addRole' | 'removeRole' | 'mute' | 'unmute' | 'bushKick' | 'bushBan' | 'isOwner' | 'isSuperUser' +>; + +/** + * @typedef {BushClientEvents} VSCodePleaseDontRemove + */
\ No newline at end of file diff --git a/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts b/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts index 0866fce..a0e65e7 100644 --- a/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts +++ b/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts @@ -14,25 +14,156 @@ import { } from 'discord.js'; import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes'; +/** + * Manages API methods for GuildMembers and stores their cache. + */ export class BushGuildMemberManager extends CachedManager<Snowflake, BushGuildMember, BushGuildMemberResolvable> { public constructor(guild: BushGuild, iterable?: Iterable<RawGuildMemberData>); public declare readonly client: BushClient; + + /** + * The guild this manager belongs to + */ public guild: BushGuild; + + /** + * Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` 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<BushGuildMember | null>; public add(user: BushUserResolvable, options: AddGuildMemberOptions): Promise<BushGuildMember>; + + /** + * 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(kickInfo => console.log(`Banned ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`)) + * .catch(console.error); + */ public ban(user: BushUserResolvable, options?: BanOptions): Promise<BushGuildMember | BushUser | Snowflake>; + + /** + * Edits a member of the guild. + * <info>The user must be a member of the guild</info> + * @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<void>; + + /** + * 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<BushGuildMember>; public fetch(options?: FetchMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>; + + /** + * Kicks a user from the guild. + * <info>The user must be a member of the guild</info> + * @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<BushGuildMember | BushUser | Snowflake>; + + /** + * Lists up to 1000 members of the guild. + * @param options Options for listing members + */ public list(options?: GuildListMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>; + + /** + * 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<null>; public prune(options?: GuildPruneMembersOptions): Promise<number>; + + /** + * Searches for members in the guild based on a query. + * @param options Options for searching members + */ public search(options: GuildSearchMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>; + + /** + * 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<BushUser>; } diff --git a/src/lib/extensions/discord.js/BushMessage.ts b/src/lib/extensions/discord.js/BushMessage.ts index 9f6d422..b442196 100644 --- a/src/lib/extensions/discord.js/BushMessage.ts +++ b/src/lib/extensions/discord.js/BushMessage.ts @@ -4,7 +4,7 @@ import type { BushGuild, BushGuildMember, BushGuildTextBasedChannel, - BushTextBasedChannels, + BushTextBasedChannel, BushUser } from '#lib'; import { Message, type If, type Partialize } from 'discord.js'; @@ -15,19 +15,23 @@ export type PartialBushMessage = Partialize< 'type' | 'system' | 'pinned' | 'tts', 'content' | 'cleanContent' | 'author' >; + +/** + * Represents a message on Discord. + */ export class BushMessage<Cached extends boolean = boolean> extends Message<Cached> { public declare readonly client: BushClient; public declare util: BushCommandUtil<BushMessage<true>>; public declare readonly guild: If<Cached, BushGuild>; public declare readonly member: BushGuildMember | null; public declare author: BushUser; - public declare readonly channel: If<Cached, BushGuildTextBasedChannel, BushTextBasedChannels>; + public declare readonly channel: If<Cached, BushGuildTextBasedChannel, BushTextBasedChannel>; public constructor(client: BushClient, data: RawMessageData) { super(client, data); } } -export interface BushMessage { +export interface BushMessage<Cached extends boolean = boolean> extends Message<Cached> { fetch(force?: boolean): Promise<BushMessage>; } diff --git a/src/lib/extensions/discord.js/BushMessageManager.d.ts b/src/lib/extensions/discord.js/BushMessageManager.d.ts index f6bc8e7..84918c0 100644 --- a/src/lib/extensions/discord.js/BushMessageManager.d.ts +++ b/src/lib/extensions/discord.js/BushMessageManager.d.ts @@ -1,4 +1,4 @@ -import { BushMessageResolvable, type BushMessage, type BushTextBasedChannels } from '#lib'; +import { BushMessageResolvable, BushTextBasedChannel, type BushMessage } from '#lib'; import { CachedManager, type BaseFetchOptions, @@ -11,17 +11,95 @@ import { } from 'discord.js'; import type { RawMessageData } from 'discord.js/typings/rawDataTypes'; +/** + * Manages API methods for Messages and holds their cache. + */ export class BushMessageManager extends CachedManager<Snowflake, BushMessage, BushMessageResolvable> { - public constructor(channel: BushTextBasedChannels, iterable?: Iterable<RawMessageData>); - public channel: BushTextBasedChannels; + public constructor(channel: BushTextBasedChannel, iterable?: Iterable<RawMessageData>); + + /** + * The channel that the messages belong to + */ + public channel: BushTextBasedChannel; + + /** + * The cache of Messages + */ public cache: Collection<Snowflake, BushMessage>; + + /** + * 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<BushMessage>; + + /** + * Deletes a message, even if it's not cached. + * @param message The message to delete + */ public delete(message: BushMessageResolvable): Promise<void>; + + /** + * 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: MessagePayload | MessageEditOptions): Promise<BushMessage>; + + /** + * Gets a message, or messages, from this channel. + * <info>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.</info> + * @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(message: Snowflake, options?: BaseFetchOptions): Promise<BushMessage>; public fetch(options?: ChannelLogsQueryOptions, cacheOptions?: BaseFetchOptions): Promise<Collection<Snowflake, BushMessage>>; + + /** + * Fetches the pinned messages of this channel and returns a collection of them. + * <info>The returned Collection does not contain any reaction data of the messages. + * Those need to be fetched separately.</info> + * @param cache 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<Collection<Snowflake, BushMessage>>; + + /** + * 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<void>; + + /** + * 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<void>; + + /** + * 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<void>; } diff --git a/src/lib/extensions/discord.js/BushMessageReaction.ts b/src/lib/extensions/discord.js/BushMessageReaction.ts index 51b439a..47d4119 100644 --- a/src/lib/extensions/discord.js/BushMessageReaction.ts +++ b/src/lib/extensions/discord.js/BushMessageReaction.ts @@ -4,6 +4,9 @@ import type { RawMessageReactionData } from 'discord.js/typings/rawDataTypes'; export type PartialBushMessageReaction = Partialize<BushMessageReaction, 'count'>; +/** + * Represents a reaction to a message. + */ export class BushMessageReaction extends MessageReaction { public declare readonly client: BushClient; public declare readonly emoji: BushGuildEmoji | BushReactionEmoji; diff --git a/src/lib/extensions/discord.js/BushNewsChannel.ts b/src/lib/extensions/discord.js/BushNewsChannel.ts index 716c57d..7df7f37 100644 --- a/src/lib/extensions/discord.js/BushNewsChannel.ts +++ b/src/lib/extensions/discord.js/BushNewsChannel.ts @@ -1,6 +1,9 @@ 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<AllowedThreadTypeForNewsChannel>; public declare guild: BushGuild; diff --git a/src/lib/extensions/discord.js/BushPresence.ts b/src/lib/extensions/discord.js/BushPresence.ts index 60408f0..f0a3ba6 100644 --- a/src/lib/extensions/discord.js/BushPresence.ts +++ b/src/lib/extensions/discord.js/BushPresence.ts @@ -2,6 +2,9 @@ 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 declare readonly member: BushGuildMember | null; diff --git a/src/lib/extensions/discord.js/BushReactionEmoji.ts b/src/lib/extensions/discord.js/BushReactionEmoji.ts index b85916c..b2a7eb0 100644 --- a/src/lib/extensions/discord.js/BushReactionEmoji.ts +++ b/src/lib/extensions/discord.js/BushReactionEmoji.ts @@ -2,6 +2,11 @@ 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; diff --git a/src/lib/extensions/discord.js/BushRole.ts b/src/lib/extensions/discord.js/BushRole.ts index bf09e2d..acf795d 100644 --- a/src/lib/extensions/discord.js/BushRole.ts +++ b/src/lib/extensions/discord.js/BushRole.ts @@ -2,6 +2,9 @@ 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 declare readonly members: Collection<Snowflake, BushGuildMember>; diff --git a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts index d33ddd3..903b43f 100644 --- a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts +++ b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts @@ -1,15 +1,18 @@ -import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannels, BushUser } from '#lib'; +import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib'; import type { APIInteractionGuildMember } from 'discord-api-types/v9'; 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<Cached extends CacheType = CacheType> extends SelectMenuInteraction<Cached> { public declare readonly channel: CacheTypeReducer< Cached, BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, BushGuildTextBasedChannel | null, - BushTextBasedChannels | null + BushTextBasedChannel | null >; public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>; public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>; diff --git a/src/lib/extensions/discord.js/BushStageChannel.ts b/src/lib/extensions/discord.js/BushStageChannel.ts index 1391757..5f6c581 100644 --- a/src/lib/extensions/discord.js/BushStageChannel.ts +++ b/src/lib/extensions/discord.js/BushStageChannel.ts @@ -2,11 +2,14 @@ import type { BushCategoryChannel, BushGuild, BushGuildMember, BushStageInstance 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 readonly instance: BushStageInstance | null; public declare readonly members: Collection<Snowflake, BushGuildMember>; public declare guild: BushGuild; public declare readonly parent: BushCategoryChannel | null; + public declare readonly stageInstance: BushStageInstance | null; public constructor(guild: BushGuild, data?: RawGuildChannelData) { super(guild, data); diff --git a/src/lib/extensions/discord.js/BushStageInstance.ts b/src/lib/extensions/discord.js/BushStageInstance.ts index 3b46bf6..80fa5cf 100644 --- a/src/lib/extensions/discord.js/BushStageInstance.ts +++ b/src/lib/extensions/discord.js/BushStageInstance.ts @@ -2,6 +2,9 @@ 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 declare readonly channel: BushStageChannel | null; public declare readonly guild: BushGuild | null; diff --git a/src/lib/extensions/discord.js/BushStoreChannel.ts b/src/lib/extensions/discord.js/BushStoreChannel.ts index 918c27b..cb75076 100644 --- a/src/lib/extensions/discord.js/BushStoreChannel.ts +++ b/src/lib/extensions/discord.js/BushStoreChannel.ts @@ -2,6 +2,10 @@ import type { BushCategoryChannel, BushClient, BushGuild, BushGuildMember } from import { StoreChannel, type Collection, type Snowflake } from 'discord.js'; import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes'; +/** + * Represents a guild store channel on Discord. + * @deprecated Store channels are deprecated and will be removed from Discord in March 2022. See [Self-serve Game Selling Deprecation](https://support-dev.discord.com/hc/en-us/articles/4414590563479) for more information + */ // eslint-disable-next-line deprecation/deprecation export class BushStoreChannel extends StoreChannel { public declare guild: BushGuild; diff --git a/src/lib/extensions/discord.js/BushTextChannel.ts b/src/lib/extensions/discord.js/BushTextChannel.ts index 57f0833..45e1200 100644 --- a/src/lib/extensions/discord.js/BushTextChannel.ts +++ b/src/lib/extensions/discord.js/BushTextChannel.ts @@ -2,6 +2,9 @@ import type { BushGuild, BushMessageManager, BushThreadManager } from '#lib'; import { 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; diff --git a/src/lib/extensions/discord.js/BushThreadChannel.ts b/src/lib/extensions/discord.js/BushThreadChannel.ts index 5f68213..4310e83 100644 --- a/src/lib/extensions/discord.js/BushThreadChannel.ts +++ b/src/lib/extensions/discord.js/BushThreadChannel.ts @@ -10,6 +10,9 @@ import type { import { 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; diff --git a/src/lib/extensions/discord.js/BushThreadManager.d.ts b/src/lib/extensions/discord.js/BushThreadManager.d.ts index c824ae7..1366d68 100644 --- a/src/lib/extensions/discord.js/BushThreadManager.d.ts +++ b/src/lib/extensions/discord.js/BushThreadManager.d.ts @@ -14,12 +14,69 @@ import { } from 'discord.js'; import type { RawThreadChannelData } from 'discord.js/typings/rawDataTypes'; +/** + * Manages API methods for {@link BushThreadChannel} objects and stores their cache. + */ export class BushThreadManager<AllowedThreadType> extends CachedManager<Snowflake, BushThreadChannel, ThreadChannelResolvable> { public constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>); + + /** + * 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: 'GUILD_PRIVATE_THREAD', + * reason: 'Needed a separate thread for moderation', + * }) + * .then(threadChannel => console.log(threadChannel)) + * .catch(console.error); + */ public create(options: ThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>; + + /** + * 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. <warn>The `force` field gets ignored + * if `options` is not a {@link ThreadChannelResolvable}</warn> + * @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<ThreadChannel | null>; public fetch(options?: FetchThreadsOptions, cacheOptions?: { cache?: boolean }): Promise<FetchedThreads>; + + /** + * 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<FetchedThreads>; + + /** + * 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<FetchedThreads>; } diff --git a/src/lib/extensions/discord.js/BushThreadMember.ts b/src/lib/extensions/discord.js/BushThreadMember.ts index fa7588e..a316046 100644 --- a/src/lib/extensions/discord.js/BushThreadMember.ts +++ b/src/lib/extensions/discord.js/BushThreadMember.ts @@ -2,6 +2,9 @@ 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 readonly guildMember: BushGuildMember | null; public declare readonly user: BushUser | null; diff --git a/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts b/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts index 7560c0e..dedf102 100644 --- a/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts +++ b/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts @@ -2,13 +2,42 @@ import type { BushClient, BushThreadChannel, BushThreadMember, BushThreadMemberR import { CachedManager, 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 class BushThreadMemberManager extends CachedManager<Snowflake, BushThreadMember, BushThreadMemberResolvable> { public constructor(thread: BushThreadChannel, iterable?: Iterable<RawThreadMemberData>); 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<Snowflake>; + + /** + * Fetches member(s) for the thread from Discord, requires access to the `GUILD_MEMBERS` gateway intent. + * @param member The member to fetch. If `undefined`, all members in the thread are fetched, and will be + * cached based on `options.cache`. If boolean, this serves the purpose of `options.cache`. + * @param options Additional options for this fetch + */ public fetch(member?: UserResolvable, options?: BaseFetchOptions): Promise<BushThreadMember>; - /** @deprecated Use `fetch(member, options)` instead. */ + + /** + * @deprecated Use `fetch(member, options)` instead. + */ public fetch(cache?: boolean): Promise<Collection<Snowflake, BushThreadMember>>; + + /** + * 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<Snowflake>; } diff --git a/src/lib/extensions/discord.js/BushUser.ts b/src/lib/extensions/discord.js/BushUser.ts index 9b2c92a..5ab288e 100644 --- a/src/lib/extensions/discord.js/BushUser.ts +++ b/src/lib/extensions/discord.js/BushUser.ts @@ -4,6 +4,9 @@ import type { RawUserData } from 'discord.js/typings/rawDataTypes'; export type PartialBushUser = Partialize<BushUser, 'username' | 'tag' | 'discriminator' | 'isOwner' | 'isSuperUser'>; +/** + * Represents a user on Discord. + */ export class BushUser extends User { public declare readonly client: BushClient; public declare readonly dmChannel: BushDMChannel | null; @@ -12,10 +15,16 @@ export class BushUser extends User { 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); } diff --git a/src/lib/extensions/discord.js/BushUserManager.d.ts b/src/lib/extensions/discord.js/BushUserManager.d.ts index 595332a..5d814da 100644 --- a/src/lib/extensions/discord.js/BushUserManager.d.ts +++ b/src/lib/extensions/discord.js/BushUserManager.d.ts @@ -1,8 +1,59 @@ -import type { BushClient, BushUser, BushUserResolvable } from '#lib'; -import { CachedManager, type BaseFetchOptions, type Snowflake } from 'discord.js'; +import type { BushClient, BushDMChannel, BushUser, BushUserResolvable } from '#lib'; +import { + CachedManager, + Message, + MessageOptions, + MessagePayload, + UserFlags, + 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 class BushUserManager extends CachedManager<Snowflake, BushUser, BushUserResolvable> { - public constructor(client: BushClient, iterable?: Iterable<RawUserData>); - public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<BushUser>; + private constructor(client: BushClient, iterable?: Iterable<RawUserData>); + + /** + * 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 DMChannel} 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<BushDMChannel>; + + /** + * Deletes a {@link DMChannel} (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<BushDMChannel>; + + /** + * 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<BushUser>; + + /** + * 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<UserFlags>; + + /** + * 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<Message>; } diff --git a/src/lib/extensions/discord.js/BushVoiceChannel.ts b/src/lib/extensions/discord.js/BushVoiceChannel.ts index d5cdafc..9f246e5 100644 --- a/src/lib/extensions/discord.js/BushVoiceChannel.ts +++ b/src/lib/extensions/discord.js/BushVoiceChannel.ts @@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildMember } 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 declare readonly members: Collection<Snowflake, BushGuildMember>; diff --git a/src/lib/extensions/discord.js/BushVoiceState.ts b/src/lib/extensions/discord.js/BushVoiceState.ts index a479143..3f19201 100644 --- a/src/lib/extensions/discord.js/BushVoiceState.ts +++ b/src/lib/extensions/discord.js/BushVoiceState.ts @@ -1,10 +1,13 @@ -import type { BushGuild, BushGuildMember, BushStageChannel, BushVoiceChannel } from '#lib'; +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 readonly channel: BushVoiceChannel | BushStageChannel | null; + public declare readonly client: BushClient; + public declare readonly channel: BushVoiceBasedChannel | null; public declare guild: BushGuild; public declare readonly member: BushGuildMember | null; diff --git a/src/lib/extensions/global.d.ts b/src/lib/extensions/global.d.ts index 1df86bb..a6f2b5a 100644 --- a/src/lib/extensions/global.d.ts +++ b/src/lib/extensions/global.d.ts @@ -1,7 +1,14 @@ /* eslint-disable no-var */ import type { BushClient, BushClientUtil } from '#lib'; declare global { + /** + * The bushbot client. + */ var client: BushClient; + + /** + * The bushbot client util. + */ var util: BushClientUtil; // eslint-disable-next-line @typescript-eslint/no-unused-vars |