diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-11-28 09:27:41 -0500 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2021-11-28 09:27:41 -0500 |
commit | 453683b57b8ff013ff25e2aaa4aa1d2e047edcb7 (patch) | |
tree | 8b98d2f30dbb6a8448602446cfacf9091667cc33 /src/lib | |
parent | de4c3dcaf172804d34ae708be1ed3e75af42f4d5 (diff) | |
download | tanzanite-453683b57b8ff013ff25e2aaa4aa1d2e047edcb7.tar.gz tanzanite-453683b57b8ff013ff25e2aaa4aa1d2e047edcb7.tar.bz2 tanzanite-453683b57b8ff013ff25e2aaa4aa1d2e047edcb7.zip |
a few small changes
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/common/Format.ts | 52 | ||||
-rw-r--r-- | src/lib/common/util/Arg.ts | 6 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClient.ts | 4 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushClientUtil.ts | 283 | ||||
-rw-r--r-- | src/lib/extensions/discord-akairo/BushCommand.ts | 309 | ||||
-rw-r--r-- | src/lib/extensions/discord.js/BushGuild.ts | 48 | ||||
-rw-r--r-- | src/lib/extensions/global.d.ts | 9 | ||||
-rw-r--r-- | src/lib/utils/BushConstants.ts | 399 | ||||
-rw-r--r-- | src/lib/utils/BushLogger.ts | 68 |
9 files changed, 795 insertions, 383 deletions
diff --git a/src/lib/common/Format.ts b/src/lib/common/Format.ts index b47be38..6cb6edc 100644 --- a/src/lib/common/Format.ts +++ b/src/lib/common/Format.ts @@ -1,5 +1,5 @@ import { type CodeBlockLang } from '#lib'; -import { Formatters, Util } from 'discord.js'; +import { EscapeMarkdownOptions, Formatters, Util } from 'discord.js'; /** * Formats and escapes content for formatting @@ -19,8 +19,8 @@ export class Format { public static codeBlock(language: CodeBlockLang, content: string): string; public static codeBlock(languageOrContent: string, content?: string): string { return typeof content === 'undefined' - ? Formatters.codeBlock(Util.escapeCodeBlock(languageOrContent)) - : Formatters.codeBlock(languageOrContent, Util.escapeCodeBlock(languageOrContent)); + ? Formatters.codeBlock(Util.escapeCodeBlock(`${languageOrContent}`)) + : Formatters.codeBlock(`${languageOrContent}`, Util.escapeCodeBlock(`${content}`)); } /** @@ -28,7 +28,7 @@ export class Format { * @param content The content to wrap. */ public static inlineCode(content: string): string { - return Formatters.inlineCode(Util.escapeInlineCode(content)); + return Formatters.inlineCode(Util.escapeInlineCode(`${content}`)); } /** @@ -36,7 +36,7 @@ export class Format { * @param content The content to wrap. */ public static italic(content: string): string { - return Formatters.italic(Util.escapeItalic(content)); + return Formatters.italic(Util.escapeItalic(`${content}`)); } /** @@ -44,7 +44,7 @@ export class Format { * @param content The content to wrap. */ public static bold(content: string): string { - return Formatters.bold(Util.escapeBold(content)); + return Formatters.bold(Util.escapeBold(`${content}`)); } /** @@ -52,7 +52,7 @@ export class Format { * @param content The content to wrap. */ public static underscore(content: string): string { - return Formatters.underscore(Util.escapeUnderline(content)); + return Formatters.underscore(Util.escapeUnderline(`${content}`)); } /** @@ -60,7 +60,7 @@ export class Format { * @param content The content to wrap. */ public static strikethrough(content: string): string { - return Formatters.strikethrough(Util.escapeStrikethrough(content)); + return Formatters.strikethrough(Util.escapeStrikethrough(`${content}`)); } /** @@ -68,6 +68,40 @@ export class Format { * @param content The content to wrap. */ public static spoiler(content: string): string { - return Formatters.spoiler(Util.escapeSpoiler(content)); + return Formatters.spoiler(Util.escapeSpoiler(`${content}`)); + } + + /** + * Escapes any Discord-flavour markdown in a string. + * @param text Content to escape + * @param options Options for escaping the markdown + */ + public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string { + return Util.escapeMarkdown(`${text}`, options); + } + + /** + * Formats input: makes it bold and escapes any other markdown + * @param text The input + */ + public static input(text: string): string { + return this.bold(this.escapeMarkdown(this.sanitizeWtlAndControl(`${text}`))); + } + + /** + * Formats input for logs: makes it highlighted + * @param text The input + */ + public static inputLog(text: string): string { + return `<<${this.sanitizeWtlAndControl(`${text}`)}>>`; + } + + /** + * Removes all characters in a string that are either control characters or change the direction of text etc. + * @param str The string you would like sanitized + */ + public static sanitizeWtlAndControl(str: string) { + // eslint-disable-next-line no-control-regex + return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, ''); } } diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts index ee9aee0..1982f4a 100644 --- a/src/lib/common/util/Arg.ts +++ b/src/lib/common/util/Arg.ts @@ -9,7 +9,11 @@ export class Arg { * @param message - Message that called the command. * @param phrase - Phrase to process. */ - public static cast(type: BushArgumentType, message: BushMessage | BushSlashMessage, phrase: string): Promise<any> { + public static cast( + type: BushArgumentType | ArgumentTypeCaster, + message: BushMessage | BushSlashMessage, + phrase: string + ): Promise<any> { return Argument.cast(type, client.commandHandler.resolver, message as Message, phrase); } diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 3339a62..45ad7ca 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -41,6 +41,7 @@ import { discordEmoji } from '../../../arguments/discordEmoji.js'; import { duration } from '../../../arguments/duration.js'; import { durationSeconds } from '../../../arguments/durationSeconds.js'; import { globalUser } from '../../../arguments/globalUser.js'; +import { messageLink } from '../../../arguments/messageLink.js'; import { permission } from '../../../arguments/permission.js'; import { roleWithDuration } from '../../../arguments/roleWithDuration.js'; import { snowflake } from '../../../arguments/snowflake.js'; @@ -289,7 +290,8 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re roleWithDuration, abbreviatedNumber, durationSeconds, - globalUser + globalUser, + messageLink }); this.sentry = Sentry; diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index aa64562..889cd6e 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -18,9 +18,11 @@ import { } from '#lib'; import { humanizeDuration } from '@notenoughupdates/humanize-duration'; import { exec } from 'child_process'; +import deepLock from 'deep-lock'; import { ClientUtil, Util as AkairoUtil } from 'discord-akairo'; import { APIMessage } from 'discord-api-types'; import { + Constants as DiscordConstants, GuildMember, Message, MessageEmbed, @@ -37,7 +39,6 @@ import { } from 'discord.js'; import got from 'got'; import _ from 'lodash'; -import moment from 'moment'; import { inspect, promisify } from 'util'; import CommandErrorListener from '../../../listeners/commands/commandError.js'; import { Format } from '../../common/Format.js'; @@ -105,10 +106,7 @@ export class BushClientUtil extends ClientUtil { * @param content The text to post * @returns The url of the posted text */ - public async haste( - content: string, - substr = false - ): Promise<{ url?: string; error?: 'content too long' | 'substr' | 'unable to post' }> { + public async haste(content: string, substr = false): Promise<HasteResults> { let isSubstr = false; if (content.length > 400_000 && !substr) { void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`)); @@ -119,7 +117,7 @@ export class BushClientUtil extends ClientUtil { } for (const url of this.#hasteURLs) { try { - const res: hastebinRes = await got.post(`${url}/documents`, { body: content }).json(); + const res: HastebinRes = await got.post(`${url}/documents`, { body: content }).json(); return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined }; } catch { void client.console.error('haste', `Unable to upload haste to ${url}`); @@ -197,7 +195,10 @@ export class BushClientUtil extends ClientUtil { } /** - * A simple utility to create and embed with the needed style for the bot + * A simple utility to create and embed with the needed style for the bot. + * @param color The color to set the embed to. + * @param author The author to set the embed to. + * @returns The generated embed. */ public createEmbed(color?: ColorResolvable, author?: User | GuildMember): MessageEmbed { if (author instanceof GuildMember) { @@ -214,15 +215,25 @@ export class BushClientUtil extends ClientUtil { return embed; } - public async mcUUID(username: string): Promise<string> { - const apiRes = (await got.get(`https://api.ashcon.app/mojang/v2/user/${username}`).json()) as uuidRes; - return apiRes.uuid.replace(/-/g, ''); + /** + * Fetches a user's uuid from the mojang api. + * @param username The username to get the uuid of. + * @returns The the uuid of the user. + */ + public async mcUUID(username: string, dashed = false): Promise<string> { + const apiRes = (await got.get(`https://api.ashcon.app/mojang/v2/user/${username}`).json()) as UuidRes; + return dashed ? apiRes.uuid : apiRes.uuid.replace(/-/g, ''); } /** * Surrounds text in a code block with the specified language and puts it in a hastebin if its too long. * * Embed Description Limit = 4096 characters * * Embed Field Limit = 1024 characters + * @param code The content of the code block. + * @param length The maximum length of the code block. + * @param language The language of the code. + * @param substr Whether or not to substring the code if it is too long. + * @returns The generated code block */ public async codeblock(code: string, length: number, language: CodeBlockLang | '' = '', substr = false): Promise<string> { let hasteOut = ''; @@ -250,15 +261,21 @@ export class BushClientUtil extends ClientUtil { } /** - * Uses {@link inspect} with custom defaults - * @param object - The object you would like to inspect - * @param options - The options you would like to use to inspect the object + * Uses {@link inspect} with custom defaults. + * @param object - The object you would like to inspect. + * @param options - The options you would like to use to inspect the object. + * @returns The inspected object. */ public inspect(object: any, options?: BushInspectOptions): string { const optionsWithDefaults = this.getDefaultInspectOptions(options); return inspect(object, optionsWithDefaults); } + /** + * Generate defaults for {@link inspect}. + * @param options The options to create defaults with. + * @returns The default options combined with the specified options. + */ private getDefaultInspectOptions(options?: BushInspectOptions): BushInspectOptions { const { showHidden = false, @@ -288,7 +305,12 @@ export class BushClientUtil extends ClientUtil { }; } - #mapCredential(old: string): string { + /** + * Maps the key of a credential with a readable version when redacting. + * @param key The key of the credential. + * @returns The readable version of the key or the original key if there isn't a mapping. + */ + #mapCredential(key: string): string { const mapping = { token: 'Main Token', devToken: 'Dev Token', @@ -297,12 +319,13 @@ export class BushClientUtil extends ClientUtil { wolframAlphaAppId: 'Wolfram|Alpha App ID', dbPassword: 'Database Password' }; - return mapping[old as keyof typeof mapping] || old; + return mapping[key as keyof typeof mapping] || key; } /** - * Redacts credentials from a string - * @param text - The text to redact credentials from + * Redacts credentials from a string. + * @param text The text to redact credentials from. + * @returns The redacted text. */ public redact(text: string) { for (const credentialName in { ...client.config.credentials, dbPassword: client.config.db.password }) { @@ -321,8 +344,13 @@ export class BushClientUtil extends ClientUtil { } /** - * Takes an any value, inspects it, redacts credentials and puts it in a codeblock - * (and uploads to hast if the content is too long) + * Takes an any value, inspects it, redacts credentials, and puts it in a codeblock + * (and uploads to hast if the content is too long). + * @param input The object to be inspect, redacted, and put into a codeblock. + * @param language The language to make the codeblock. + * @param inspectOptions The options for {@link BushClientUtil.inspect}. + * @param length The maximum length that the codeblock can be. + * @returns The generated codeblock. */ public async inspectCleanRedactCodeblock( input: any, @@ -338,17 +366,35 @@ export class BushClientUtil extends ClientUtil { return this.codeblock(input, length, language, true); } - public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions) { + /** + * Takes an any value, inspects it, redacts credentials, and uploads it to haste. + * @param input The object to be inspect, redacted, and upload. + * @param inspectOptions The options for {@link BushClientUtil.inspect}. + * @returns The {@link HasteResults}. + */ + public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions): Promise<HasteResults> { input = typeof input !== 'string' ? this.inspect(input, inspectOptions ?? undefined) : input; input = this.redact(input); return this.haste(input, true); } - public inspectAndRedact(input: any, inspectOptions?: BushInspectOptions) { + /** + * Takes an any value, inspects it and redacts credentials. + * @param input The object to be inspect and redacted. + * @param inspectOptions The options for {@link BushClientUtil.inspect}. + * @returns The redacted and inspected object. + */ + public inspectAndRedact(input: any, inspectOptions?: BushInspectOptions): string { input = typeof input !== 'string' ? this.inspect(input, inspectOptions ?? undefined) : input; return this.redact(input); } + /** + * Responds to a slash command interaction. + * @param interaction The interaction to respond to. + * @param responseOptions The options for the response. + * @returns The message sent. + */ public async slashRespond( interaction: CommandInteraction, responseOptions: BushSlashSendMessageType | BushSlashEditMessageType @@ -364,7 +410,8 @@ export class BushClientUtil extends ClientUtil { } /** - * Gets a a configured channel as a TextChannel + * Gets a a configured channel as a TextChannel. + * @channel The channel to retrieve. */ public async getConfigChannel(channel: keyof typeof client['config']['channels']): Promise<TextChannel> { return (await client.channels.fetch(client.config.channels[channel])) as unknown as TextChannel; @@ -375,7 +422,7 @@ export class BushClientUtil extends ClientUtil { * @param array The array to combine. * @param conjunction The conjunction to use. * @param ifEmpty What to return if the array is empty. - * @returns The combined elements or `ifEmpty` + * @returns The combined elements or `ifEmpty`. * * @example * const permissions = oxford(['ADMINISTRATOR', 'SEND_MESSAGES', 'MANAGE_MESSAGES'], 'and', 'none'); @@ -391,10 +438,16 @@ export class BushClientUtil extends ClientUtil { return array.join(', '); } - public async insertOrRemoveFromGlobal( + /** + * Add or remove an element from an array stored in the Globals database. + * @param action Either `add` or `remove` an element. + * @param key The key of the element in the global cache to update. + * @param value The value to add/remove from the array. + */ + public async insertOrRemoveFromGlobal<K extends keyof typeof client['cache']['global']>( action: 'add' | 'remove', - key: keyof typeof client['cache']['global'], - value: any + key: K, + value: typeof client['cache']['global'][K][0] ): Promise<Global | void> { const row = (await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment })); @@ -406,7 +459,26 @@ export class BushClientUtil extends ClientUtil { } /** + * Updates an element in the Globals database. + * @param key The key in the global cache to update. + * @param value The value to set the key to. + */ + public async setGlobal<K extends keyof typeof client['cache']['global']>( + key: K, + value: typeof client['cache']['global'][K] + ): Promise<Global | void> { + const row = + (await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment })); + row[key] = value; + client.cache.global[key] = value; + return await row.save().catch((e) => this.handleError('setGlobal', e)); + } + + /** * Add or remove an item from an array. All duplicates will be removed. + * @param action Either `add` or `remove` an element. + * @param array The array to add/remove an element from. + * @param value The element to add/remove from the array. */ public addOrRemoveFromArray<T>(action: 'add' | 'remove', array: T[], value: T): T[] { const set = new Set(array); @@ -416,15 +488,21 @@ export class BushClientUtil extends ClientUtil { /** * Surrounds a string to the begging an end of each element in an array. - * @param array - The array you want to surround. - * @param surroundChar1 - The character placed in the beginning of the element. - * @param surroundChar2 - The character placed in the end of the element. Defaults to `surroundChar1`. + * @param array The array you want to surround. + * @param surroundChar1 The character placed in the beginning of the element. + * @param surroundChar2 The character placed in the end of the element. Defaults to `surroundChar1`. */ public surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] { return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`); } - public parseDuration(content: string, remove = true): { duration: number | null; contentWithoutTime: string | null } { + /** + * Gets the duration from a specified string. + * @param content The string to look for a duration in. + * @param remove Whether or not to remove the duration from the original string. + * @returns The {@link ParsedDuration}. + */ + public parseDuration(content: string, remove = true): ParsedDuration { if (!content) return { duration: 0, contentWithoutTime: null }; // eslint-disable-next-line prefer-const @@ -434,10 +512,11 @@ export class BushClientUtil extends ClientUtil { let contentWithoutTime = ` ${content}`; for (const unit in BushConstants.TimeUnits) { - const regex = BushConstants.TimeUnits[unit].match; + const regex = BushConstants.TimeUnits[unit as keyof typeof BushConstants.TimeUnits].match; const match = regex.exec(contentWithoutTime); const value = Number(match?.groups?.[unit]); - if (!isNaN(value)) (duration as unknown as number) += value * BushConstants.TimeUnits[unit].value; + if (!isNaN(value)) + (duration as unknown as number) += value * BushConstants.TimeUnits[unit as keyof typeof BushConstants.TimeUnits].value; if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); } @@ -446,16 +525,33 @@ export class BushClientUtil extends ClientUtil { return { duration, contentWithoutTime }; } + /** + * Converts a duration in milliseconds to a human readable form. + * @param duration The duration in milliseconds to convert. + * @param largest The maximum number of units to display for the duration. + * @returns A humanized string of the duration. + */ public humanizeDuration(duration: number, largest?: number): string { if (largest) return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2, largest })!; else return humanizeDuration(duration, { language: 'en', maxDecimalPoints: 2 })!; } + /** + * Creates a formatted relative timestamp from a duration in milliseconds. + * @param duration The duration in milliseconds. + * @returns The formatted relative timestamp. + */ public timestampDuration(duration: number): string { - return `<t:${Math.round(duration / 1000)}:R>`; + return `<t:${Math.round(new Date().getTime() / 1_000 + duration / 1_000)}:R>`; } /** + * Creates a timestamp from a date. + * @param date The date to create a timestamp from. + * @param style The style of the timestamp. + * @returns The formatted timestamp. + * + * @see * **Styles:** * - **t**: Short Time * - **T**: Long Time @@ -470,33 +566,24 @@ export class BushClientUtil extends ClientUtil { style: 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R' = 'f' ): D extends Date ? string : undefined { if (!date) return date as unknown as D extends Date ? string : undefined; - return `<t:${Math.round(date.getTime() / 1000)}:${style}>` as unknown as D extends Date ? string : undefined; - } - - public dateDelta(date: Date, largest?: number) { - return this.humanizeDuration(moment(date).diff(moment()), largest ?? 3); + return `<t:${Math.round(date.getTime() / 1_000)}:${style}>` as unknown as D extends Date ? string : undefined; } - public async findUUID(player: string): Promise<string> { - try { - const raw = await got.get(`https://api.ashcon.app/mojang/v2/user/${player}`); - let profile: MojangProfile; - if (raw.statusCode == 200) { - profile = JSON.parse(raw.body); - } else { - throw new Error('invalid player'); - } - - if (raw.statusCode == 200 && profile && profile.uuid) { - return profile.uuid.replace(/-/g, ''); - } else { - throw new Error(`Could not fetch the uuid for ${player}.`); - } - } catch (e) { - throw new Error('An error has occurred.'); - } + /** + * Creates a human readable representation between a date and the current time. + * @param date The date to be compared with the current time. + * @param largest The maximum number of units to display for the duration. + * @returns A humanized string of the delta. + */ + public dateDelta(date: Date, largest?: number): string { + return this.humanizeDuration(new Date().getTime() - date.getTime(), largest ?? 3); } + /** + * Convert a hex code to an rbg value. + * @param hex The hex code to convert. + * @returns The rbg value. + */ public hexToRgb(hex: string): string { const arrBuff = new ArrayBuffer(4); const vw = new DataView(arrBuff); @@ -507,20 +594,34 @@ export class BushClientUtil extends ClientUtil { } /* eslint-disable @typescript-eslint/no-unused-vars */ - public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) {} + public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) { + // todo: implement lockdowns + } /* eslint-enable @typescript-eslint/no-unused-vars */ + /** + * Capitalize the first letter of a string. + * @param string The string to capitalize the first letter of. + * @returns The string with the first letter capitalized. + */ public capitalizeFirstLetter(string: string): string { return string.charAt(0)?.toUpperCase() + string.slice(1); } /** * Wait an amount in seconds. + * @param s 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)); } + /** + * Send a message in the error logging channel and console for an error. + * @param context + * @param error + */ public async handleError(context: string, error: Error) { await client.console.error(_.camelCase(context), `An error occurred:\n${error?.stack ?? (error as any)}`, false); await client.console.channelError({ @@ -553,24 +654,24 @@ export class BushClientUtil extends ClientUtil { if (!apiRes) return undefined; if (!apiRes.pronouns) throw new Error('apiRes.pronouns is undefined'); - return client.constants.pronounMapping[apiRes.pronouns]; + return client.constants.pronounMapping[apiRes.pronouns!]!; } - // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class - // answer by Bruno Grieder - public getMethods(_obj: any): string { + public getMethods(obj: Record<string, any>): string { + // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class + // answer by Bruno Grieder let props: string[] = []; - let obj: any = new Object(_obj); + let obj_: Record<string, any> = new Object(obj); do { - const l = Object.getOwnPropertyNames(obj) - .concat(Object.getOwnPropertySymbols(obj).map((s) => s.toString())) + const l = Object.getOwnPropertyNames(obj_) + .concat(Object.getOwnPropertySymbols(obj_).map((s) => s.toString())) .sort() .filter( (p, i, arr) => - typeof Object.getOwnPropertyDescriptor(obj, p)?.['get'] !== 'function' && // ignore getters - typeof Object.getOwnPropertyDescriptor(obj, p)?.['set'] !== 'function' && // ignore setters - typeof obj[p] === 'function' && //only the methods + typeof Object.getOwnPropertyDescriptor(obj_, p)?.['get'] !== 'function' && // ignore getters + typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters + typeof obj_[p] === 'function' && //only the methods p !== 'constructor' && //not the constructor (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype props.indexOf(p) === -1 //not overridden in a child @@ -580,10 +681,10 @@ export class BushClientUtil extends ClientUtil { props = props.concat( l.map( (p) => - `${obj[p] && obj[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${ - reg.exec(obj[p].toString())?.[1] + `${obj_[p] && obj_[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${ + reg.exec(obj_[p].toString())?.[1] ? reg - .exec(obj[p].toString())?.[1] + .exec(obj_[p].toString())?.[1] .split(', ') .map((arg) => arg.split('=')[0].trim()) .join(', ') @@ -592,21 +693,13 @@ export class BushClientUtil extends ClientUtil { ) ); } while ( - (obj = Object.getPrototypeOf(obj)) && //walk-up the prototype chain - Object.getPrototypeOf(obj) //not the the Object prototype methods (hasOwnProperty, etc...) + (obj_ = Object.getPrototypeOf(obj_)) && //walk-up the prototype chain + Object.getPrototypeOf(obj_) //not the the Object prototype methods (hasOwnProperty, etc...) ); return props.join('\n'); } - /** - * Removes all characters in a string that are either control characters or change the direction of text etc. - */ - public sanitizeWtlAndControl(str: string) { - // eslint-disable-next-line no-control-regex - return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, ''); - } - public async uploadImageToImgur(image: string) { const clientId = this.client.config.credentials.imgurClientId; @@ -667,6 +760,14 @@ export class BushClientUtil extends ClientUtil { : message.util.parsed?.prefix ?? client.config.prefix; } + public get deepFreeze() { + return deepLock; + } + + public static get deepFreeze() { + return deepLock; + } + public get arg() { return Arg; } @@ -686,6 +787,13 @@ export class BushClientUtil extends ClientUtil { } /** + * Discord.js's Util constants + */ + public get discordConstants() { + return DiscordConstants + } + + /** * discord-akairo's Util class */ public get akairo() { @@ -693,11 +801,11 @@ export class BushClientUtil extends ClientUtil { } } -interface hastebinRes { +interface HastebinRes { key: string; } -export interface uuidRes { +export interface UuidRes { uuid: string; username: string; username_history?: { username: string }[] | null; @@ -716,7 +824,12 @@ export interface uuidRes { created_at: string; } -interface MojangProfile { - username: string; - uuid: string; +export interface HasteResults { + url?: string; + error?: 'content too long' | 'substr' | 'unable to post'; +} + +export interface ParsedDuration { + duration: number | null; + contentWithoutTime: string | null; } diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts index 1494aee..8872831 100644 --- a/src/lib/extensions/discord-akairo/BushCommand.ts +++ b/src/lib/extensions/discord-akairo/BushCommand.ts @@ -1,13 +1,23 @@ import { type BushClient, type BushCommandHandler, type BushMessage, type BushSlashMessage } from '#lib'; import { + AkairoApplicationCommandAutocompleteOption, + AkairoApplicationCommandChannelOptionData, + AkairoApplicationCommandChoicesData, + AkairoApplicationCommandNonOptionsData, + AkairoApplicationCommandNumericOptionData, + AkairoApplicationCommandOptionData, + AkairoApplicationCommandSubCommandData, + AkairoApplicationCommandSubGroupData, Command, + MissingPermissionSupplier, + SlashOption, + SlashResolveTypes, type ArgumentOptions, - type ArgumentPromptOptions, type ArgumentTypeCaster, type CommandOptions } from 'discord-akairo'; import { BaseArgumentType } from 'discord-akairo/dist/src/struct/commands/arguments/Argument'; -import { type PermissionResolvable, type Snowflake } from 'discord.js'; +import { ApplicationCommandOptionChoice, type PermissionResolvable, type Snowflake } from 'discord.js'; export type BaseBushArgumentType = | BaseArgumentType @@ -18,14 +28,76 @@ export type BaseBushArgumentType = | 'discordEmoji' | 'roleWithDuration' | 'abbreviatedNumber' - | 'globalUser'; + | 'globalUser' + | 'messageLink' export type BushArgumentType = BaseBushArgumentType | RegExp; -interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type'> { +interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt'> { id: string; - description?: string; - prompt?: ArgumentPromptOptions; + description: string; + + /** + * The message sent for the prompt and the slash command description. + */ + prompt?: string; + + /** + * The message set for the retry prompt. + */ + retry?: string; + + /** + * Whether or not the argument is optional. + */ + optional?: boolean; + + /** + * The type used for slash commands. Set to false to disable this argument for slash commands. + */ + slashType: AkairoApplicationCommandOptionData['type'] | false; + + /** + * Allows you to get a discord resolved object + * + * ex. get the resolved member object when the type is `USER` + */ + slashResolve?: SlashResolveTypes; + + /** + * The choices of the option for the user to pick from + */ + choices?: ApplicationCommandOptionChoice[]; + + /** + * Whether the option is an autocomplete option + */ + autocomplete?: boolean; + + /** + * When the option type is channel, the allowed types of channels that can be selected + */ + channelTypes?: AkairoApplicationCommandChannelOptionData['channelTypes']; + + /** + * The minimum value for an `INTEGER` or `NUMBER` option + */ + minValue?: number; + + /** + * The maximum value for an `INTEGER` or `NUMBER` option + */ + maxValue?: number; + + /** + * Restrict this argument to only slash or only text commands. + */ + only?: 'slash' | 'text'; + + /** + * Readable type for the help command. + */ + readableType?: string; } export interface BushArgumentOptions extends BaseBushArgumentOptions { @@ -93,7 +165,7 @@ export interface CustomBushArgumentOptions extends BaseBushArgumentOptions { export type BushMissingPermissionSupplier = (message: BushMessage | BushSlashMessage) => Promise<any> | any; -export interface BushCommandOptions extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions'> { +export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'> { /** * Whether the command is hidden from the help command. */ @@ -109,12 +181,24 @@ export interface BushCommandOptions extends Omit<CommandOptions, 'userPermission */ restrictedGuilds?: Snowflake[]; - description: { - content: string; - usage: string[]; - examples: string[]; - }; + /** + * The description of the command. + */ + description: string; + + /** + * Show how to use the command. + */ + usage: string[]; + /** + * Examples for how to use the command. + */ + examples: string[]; + + /** + * The arguments for the command. + */ args?: BushArgumentOptions[] & CustomBushArgumentOptions[]; category: string; @@ -138,6 +222,33 @@ export interface BushCommandOptions extends Omit<CommandOptions, 'userPermission * Permissions required by the user to run this command. */ userPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier; + + /** + * Restrict this argument to owners + */ + ownerOnly?: boolean; + + /** + * 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'>; + +export interface ArgsInfo { + id: string; + description: string; + optional?: boolean; + slashType: AkairoApplicationCommandOptionData['type'] | false; + slashResolve?: SlashResolveTypes; + only?: 'slash' | 'text'; + type: string; } export class BushCommand extends Command { @@ -145,18 +256,29 @@ export class BushCommand extends Command { public declare handler: BushCommandHandler; - public declare description: { - content: string; - usage: string[]; - examples: string[]; - }; + public declare description: string; + + /** + * Show how to use the command. + */ + public usage: string[]; + + /** + * Examples for how to use the command. + */ + public examples: string[]; /** - * The command's options + * The options sent to the constructor */ public options: BushCommandOptions; /** + * The options sent to the super call + */ + public parsedOptions: CommandOptions; + + /** * The channels the command is limited to run in. */ public restrictedChannels: Snowflake[] | undefined; @@ -181,24 +303,138 @@ export class BushCommand extends Command { */ public bypassChannelBlacklist: boolean; + /** + * Info about the arguments for the help command. + */ + public argsInfo?: ArgsInfo[]; + public constructor(id: string, options: BushCommandOptions) { - if (options.args && typeof options.args !== 'function') { - options.args.forEach((_, index: number) => { - if ('customType' in options.args![index]) { - // @ts-expect-error: shut - if (!options.args[index]['type']) options.args[index]['type'] = options.args[index]['customType']; - delete options.args![index]['customType']; + const options_ = options as BaseBushCommandOptions; + + if (options_.args && typeof options_.args !== 'function') { + options_.args.forEach((_, index: number) => { + if ('customType' in (options_.args?.[index] ?? {})) { + if (!options_.args![index]['type']) options_.args![index]['type'] = options_.args![index]['customType']! as any; + delete options_.args![index]['customType']; } }); } - // incompatible options - super(id, options as any); - this.options = options; - this.hidden = Boolean(options.hidden); - this.restrictedChannels = options.restrictedChannels; - this.restrictedGuilds = options.restrictedGuilds; - this.pseudo = Boolean(options.pseudo); - this.bypassChannelBlacklist = Boolean(options.bypassChannelBlacklist); + + 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; + } + if ('type' in arg) newArg.type = arg.type; + 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); + } + } + newOptions.args = newTextArgs; + 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); + + if (options_.args || options_.helpArgs) { + const argsInfo: ArgsInfo[] = []; + + for (const arg of (options_.args ?? options_.helpArgs)!) { + argsInfo.push({ + id: arg.id, + description: arg.description, + optional: arg.optional, + slashType: arg.slashType, + slashResolve: arg.slashResolve, + only: arg.only, + type: (arg.readableType ?? arg.type) as string + }); + } + + this.argsInfo = argsInfo; + } + + this.description = options_.description; + this.usage = options_.usage; + this.examples = options_.examples; + this.options = options_; + this.parsedOptions = newOptions; + this.hidden = !!options_.hidden; + this.restrictedChannels = options_.restrictedChannels; + this.restrictedGuilds = options_.restrictedGuilds; + this.pseudo = !!options_.pseudo; + this.bypassChannelBlacklist = !!options_.bypassChannelBlacklist; } } @@ -206,3 +442,12 @@ export interface BushCommand { exec(message: BushMessage, args: any): any; exec(message: BushMessage | BushSlashMessage, args: any): any; } + +type SlashOptionKeys = + | keyof AkairoApplicationCommandSubGroupData + | keyof AkairoApplicationCommandNonOptionsData + | keyof AkairoApplicationCommandChannelOptionData + | keyof AkairoApplicationCommandChoicesData + | keyof AkairoApplicationCommandAutocompleteOption + | keyof AkairoApplicationCommandNumericOptionData + | keyof AkairoApplicationCommandSubCommandData; diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index e3b39d3..9c272ff 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -9,8 +9,9 @@ import type { GuildLogType, GuildModel } from '#lib'; -import { Guild, type MessageOptions, type UserResolvable } from 'discord.js'; +import { Guild, MessagePayload, type MessageOptions, type UserResolvable } from 'discord.js'; import type { RawGuildData } from 'discord.js/typings/rawDataTypes'; +import _ from 'lodash'; import { Moderation } from '../../common/Moderation.js'; import { Guild as GuildDB } from '../../models/Guild.js'; import { ModLogType } from '../../models/ModLog.js'; @@ -24,29 +25,52 @@ export class BushGuild extends Guild { super(client, data); } + /** + * Checks if the guild has a certain custom feature. + * @param feature The feature to check for + */ public async hasFeature(feature: GuildFeatures): Promise<boolean> { const features = await this.getSetting('enabledFeatures'); return features.includes(feature); } + /** + * Adds a custom feature to the guild. + * @param feature The feature to add + * @param moderator The moderator responsible for adding a feature + */ public async addFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> { const features = await this.getSetting('enabledFeatures'); const newFeatures = util.addOrRemoveFromArray('add', features, feature); return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; } + /** + * Removes a custom feature from the guild. + * @param feature The feature to remove + * @param moderator The moderator responsible for removing a feature + */ public async removeFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> { const features = await this.getSetting('enabledFeatures'); const newFeatures = util.addOrRemoveFromArray('remove', features, feature); return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; } + /** + * Makes a custom feature the opposite of what it was before + * @param feature The feature to toggle + * @param moderator The moderator responsible for toggling a feature + */ public async toggleFeature(feature: GuildFeatures, moderator?: BushGuildMember): Promise<GuildModel['enabledFeatures']> { return (await this.hasFeature(feature)) ? await this.removeFeature(feature, moderator) : await this.addFeature(feature, moderator); } + /** + * Fetches a custom setting for the guild + * @param setting The setting to get + */ public async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> { return ( client.cache.guilds.get(this.id)?.[setting] ?? @@ -54,6 +78,12 @@ export class BushGuild extends Guild { ); } + /** + * Sets a custom setting for the guild + * @param setting The setting to change + * @param value The value to change the setting to + * @param moderator The moderator to responsible for changing the setting + */ public async setSetting<K extends Exclude<keyof GuildModel, 'id'>>( setting: K, value: GuildDB[K], @@ -208,13 +238,25 @@ export class BushGuild extends Guild { } /** - * Sends a message to the guild's specified logging channel. + * Sends a message to the guild's specified logging channel + * @param logType The corresponding channel that the message will be sent to + * @param message The parameters for {@link BushTextChannel.send} */ - public async sendLogChannel(logType: GuildLogType, message: MessageOptions) { + public async sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions) { const logChannel = await this.getLogChannel(logType); if (!logChannel || logChannel.type !== 'GUILD_TEXT') return; if (!logChannel.permissionsFor(this.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS'])) return; return await logChannel.send(message).catch(() => null); } + + /** + * Sends a formatted error message in a guild's error log channel + * @param title The title of the error embed + * @param message The description of the error embed + */ + public async error(title: string, message: string) { + void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); + void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] }); + } } diff --git a/src/lib/extensions/global.d.ts b/src/lib/extensions/global.d.ts index 1a30056..8427873 100644 --- a/src/lib/extensions/global.d.ts +++ b/src/lib/extensions/global.d.ts @@ -4,4 +4,13 @@ declare global { var client: BushClient; var util: BushClientUtil; var __rootdir__: string; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface ReadonlyArray<T> { + includes<S, R extends `${Extract<S, string>}`>( + this: ReadonlyArray<R>, + searchElement: S, + fromIndex?: number + ): searchElement is R & S; + } } diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index 52b0f40..434a0a7 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -1,12 +1,28 @@ -import { Constants, type ConstantsColors } from 'discord.js'; +import { Constants } from 'discord.js'; +import { BushClientUtil } from '../extensions/discord-akairo/BushClientUtil.js'; const rawCapeUrl = 'https://raw.githubusercontent.com/NotEnoughUpdates/capes/master/'; export class BushConstants { - public static get emojis() { - return BushEmojis; - } + public static emojis = BushClientUtil.deepFreeze({ + success: '<:success:837109864101707807>', + warn: '<:warn:848726900876247050>', + error: '<:error:837123021016924261>', + successFull: '<:success_full:850118767576088646>', + warnFull: '<:warn_full:850118767391539312>', + errorFull: '<:error_full:850118767295201350>', + mad: '<:mad:783046135392239626>', + join: '<:join:850198029809614858>', + leave: '<:leave:850198048205307919>', + loading: '<a:Loading:853419254619963392>', + offlineCircle: '<:offline:787550565382750239>', + dndCircle: '<:dnd:787550487633330176>', + idleCircle: '<:idle:787550520956551218>', + onlineCircle: '<:online:787550449435803658>', + cross: '<:cross:878319362539421777>', + check: '<:check:878320135297961995>' + } as const); - public static colors: bushColors = { + public static colors = BushClientUtil.deepFreeze({ default: '#1FD8F1', error: '#EF4947', warn: '#FEBA12', @@ -29,11 +45,11 @@ export class BushConstants { darkGray: '#7a7a7a', black: '#000000', orange: '#E86100', - discord: Constants.Colors - }; + discord: Object.assign({}, Constants.Colors) + } as const); // Somewhat stolen from @Mzato0001 - public static TimeUnits: { [key: string]: { match: RegExp; value: number } } = { + public static TimeUnits = BushClientUtil.deepFreeze({ milliseconds: { match: / (?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im, value: 1 @@ -66,19 +82,43 @@ export class BushConstants { match: / (?:(?<years>-?(?:\d+)?\.?\d+) *(?:years?|y))/im, value: 1000 * 60 * 60 * 24 * 365.25 //leap years } - }; + } as const); - public static regex = { + public static regex = BushClientUtil.deepFreeze({ snowflake: /\d{15,21}/im, - discordEmoji: /<a?:(?<name>[a-zA-Z0-9_]+):(?<id>\d{15,21})>/im - }; + discordEmoji: /<a?:(?<name>[a-zA-Z0-9_]+):(?<id>\d{15,21})>/im, - public static get pronounMapping() { - return PronounMap; - } + //stolen from geek + messageLink: + /(?:ptb\.|canary\.|staging\.|lc\.)?(?:discord(?:app)?|inv)\.(?:com|wtf)?\/channels\/(?<guild_id>\d{15,21}|@me)\/(?<channel_id>\d{15,21})\/(?<message_id>\d{15,21})/im + } as const); + + public static pronounMapping = BushClientUtil.deepFreeze({ + unspecified: 'Unspecified', + hh: 'He/Him', + hi: 'He/It', + hs: 'He/She', + ht: 'He/They', + ih: 'It/Him', + ii: 'It/Its', + is: 'It/She', + it: 'It/They', + shh: 'She/He', + sh: 'She/Her', + si: 'She/It', + st: 'She/They', + th: 'They/He', + ti: 'They/It', + ts: 'They/She', + tt: 'They/Them', + any: 'Any pronouns', + other: 'Other pronouns', + ask: 'Ask me my pronouns', + avoid: 'Avoid pronouns, use my name' + } as const); /** A bunch of mappings */ - public static mappings = { + public static mappings = BushClientUtil.deepFreeze({ guilds: { bush: '516977525906341928', tree: '767448775450820639', @@ -326,25 +366,129 @@ export class BushConstants { 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'], 'DJ': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'] } - }; + } as const); - public static get ArgumentMatches() { - return ArgumentMatchesEnum; - } + public static ArgumentMatches = BushClientUtil.deepFreeze({ + PHRASE: 'phrase', + FLAG: 'flag', + OPTION: 'option', + REST: 'rest', + SEPARATE: 'separate', + TEXT: 'text', + CONTENT: 'content', + REST_CONTENT: 'restContent', + NONE: 'none' + } as const); - public static get ArgumentTypes() { - return BushArgumentTypesEnum; - } + public static ArgumentTypes = BushClientUtil.deepFreeze({ + STRING: 'string', + LOWERCASE: 'lowercase', + UPPERCASE: 'uppercase', + CHAR_CODES: 'charCodes', + NUMBER: 'number', + INTEGER: 'integer', + BIGINT: 'bigint', + EMOJINT: 'emojint', + URL: 'url', + DATE: 'date', + COLOR: 'color', + USER: 'user', + USERS: 'users', + MEMBER: 'member', + MEMBERS: 'members', + RELEVANT: 'relevant', + RELEVANTS: 'relevants', + CHANNEL: 'channel', + CHANNELS: 'channels', + TEXT_CHANNEL: 'textChannel', + TEXT_CHANNELS: 'textChannels', + VOICE_CHANNEL: 'voiceChannel', + VOICE_CHANNELS: 'voiceChannels', + CATEGORY_CHANNEL: 'categoryChannel', + CATEGORY_CHANNELS: 'categoryChannels', + NEWS_CHANNEL: 'newsChannel', + NEWS_CHANNELS: 'newsChannels', + STORE_CHANNEL: 'storeChannel', + STORE_CHANNELS: 'storeChannels', + STAGE_CHANNEL: 'stageChannel', + STAGE_CHANNELS: 'stageChannels', + THREAD_CHANNEL: 'threadChannel', + THREAD_CHANNELS: 'threadChannels', + ROLE: 'role', + ROLES: 'roles', + EMOJI: 'emoji', + EMOJIS: 'emojis', + GUILD: 'guild', + GUILDS: 'guilds', + MESSAGE: 'message', + GUILD_MESSAGE: 'guildMessage', + RELEVANT_MESSAGE: 'relevantMessage', + INVITE: 'invite', + USER_MENTION: 'userMention', + MEMBER_MENTION: 'memberMention', + CHANNEL_MENTION: 'channelMention', + ROLE_MENTION: 'roleMention', + EMOJI_MENTION: 'emojiMention', + COMMAND_ALIAS: 'commandAlias', + COMMAND: 'command', + INHIBITOR: 'inhibitor', + LISTENER: 'listener', + TASK: 'task', + CONTEXT_MENU_COMMAND: 'contextMenuCommand', + DURATION: 'duration', + CONTENT_WITH_DURATION: 'contentWithDuration', + PERMISSION: 'permission', + SNOWFLAKE: 'snowflake', + DISCORD_EMOJI: 'discordEmoji', + ROLE_WITH_DURATION: 'roleWithDuration', + ABBREVIATED_NUMBER: 'abbreviatedNumber', + GLOBAL_USER: 'globalUser' + } as const); - public static get BlockedReasons() { - return BushBlockedReasonsEnum; - } + public static BlockedReasons = BushClientUtil.deepFreeze({ + CLIENT: 'client', + BOT: 'bot', + OWNER: 'owner', + SUPER_USER: 'superUser', + GUILD: 'guild', + DM: 'dm', + AUTHOR_NOT_FOUND: 'authorNotFound', + NOT_NSFW: 'notNsfw', + DISABLED_GUILD: 'disabledGuild', + DISABLED_GLOBAL: 'disabledGlobal', + ROLE_BLACKLIST: 'roleBlacklist', + USER_GUILD_BLACKLIST: 'userGuildBlacklist', + USER_GLOBAL_BLACKLIST: 'userGlobalBlacklist', + RESTRICTED_GUILD: 'restrictedGuild', + CHANNEL_GUILD_BLACKLIST: 'channelGuildBlacklist', + CHANNEL_GLOBAL_BLACKLIST: 'channelGlobalBlacklist', + RESTRICTED_CHANNEL: 'restrictedChannel' + } as const); - public static get CommandHandlerEvents() { - return BushCommandHandlerEventsEnum; - } + public static CommandHandlerEvents = BushClientUtil.deepFreeze({ + COMMAND_BLOCKED: 'commandBlocked', + COMMAND_BREAKOUT: 'commandBreakout', + COMMAND_CANCELLED: 'commandCancelled', + COMMAND_FINISHED: 'commandFinished', + COMMAND_INVALID: 'commandInvalid', + COMMAND_LOCKED: 'commandLocked', + COMMAND_STARTED: 'commandStarted', + COOLDOWN: 'cooldown', + ERROR: 'error', + IN_PROMPT: 'inPrompt', + MESSAGE_BLOCKED: 'messageBlocked', + MESSAGE_INVALID: 'messageInvalid', + MISSING_PERMISSIONS: 'missingPermissions', + SLASH_BLOCKED: 'slashBlocked', + SLASH_ERROR: 'slashError', + SLASH_FINISHED: 'slashFinished', + SLASH_MISSING_PERMISSIONS: 'slashMissingPermissions', + SLASH_NOT_FOUND: 'slashNotFound', + SLASH_STARTED: 'slashStarted', + SLASH_ONLY: 'slashOnly' + } as const); - public static moulberryBushRoleMap = [ + public static moulberryBushRoleMap = BushClientUtil.deepFreeze([ { name: '*', id: '792453550768390194' }, { name: 'Admin Perms', id: '746541309853958186' }, { name: 'Sr. Moderator', id: '782803470205190164' }, @@ -370,197 +514,8 @@ export class BushConstants { { name: 'No VC', id: '788850482554208267' }, { name: 'No Giveaways', id: '808265422334984203' }, { name: 'No Support', id: '790247359824396319' } - ]; -} - -export enum PronounMap { - unspecified = 'Unspecified', - hh = 'He/Him', - hi = 'He/It', - hs = 'He/She', - ht = 'He/They', - ih = 'It/Him', - ii = 'It/Its', - is = 'It/She', - it = 'It/They', - shh = 'She/He', - sh = 'She/Her', - si = 'She/It', - st = 'She/They', - th = 'They/He', - ti = 'They/It', - ts = 'They/She', - tt = 'They/Them', - any = 'Any pronouns', - other = 'Other pronouns', - ask = 'Ask me my pronouns', - avoid = 'Avoid pronouns, use my name' -} - -export enum BushEmojis { - success = '<:success:837109864101707807>', - warn = '<:warn:848726900876247050>', - error = '<:error:837123021016924261>', - successFull = '<:success_full:850118767576088646>', - warnFull = '<:warn_full:850118767391539312>', - errorFull = '<:error_full:850118767295201350>', - mad = '<:mad:783046135392239626>', - join = '<:join:850198029809614858>', - leave = '<:leave:850198048205307919>', - loading = '<a:Loading:853419254619963392>', - offlineCircle = '<:offline:787550565382750239>', - dndCircle = '<:dnd:787550487633330176>', - idleCircle = '<:idle:787550520956551218>', - onlineCircle = '<:online:787550449435803658>', - cross = '<:cross:878319362539421777>', - check = '<:check:878320135297961995>' -} - -export enum ArgumentMatchesEnum { - PHRASE = 'phrase', - FLAG = 'flag', - OPTION = 'option', - REST = 'rest', - SEPARATE = 'separate', - TEXT = 'text', - CONTENT = 'content', - REST_CONTENT = 'restContent', - NONE = 'none' -} - -export enum BushArgumentTypesEnum { - STRING = 'string', - LOWERCASE = 'lowercase', - UPPERCASE = 'uppercase', - CHAR_CODES = 'charCodes', - NUMBER = 'number', - INTEGER = 'integer', - BIGINT = 'bigint', - EMOJINT = 'emojint', - URL = 'url', - DATE = 'date', - COLOR = 'color', - USER = 'user', - USERS = 'users', - MEMBER = 'member', - MEMBERS = 'members', - RELEVANT = 'relevant', - RELEVANTS = 'relevants', - CHANNEL = 'channel', - CHANNELS = 'channels', - TEXT_CHANNEL = 'textChannel', - TEXT_CHANNELS = 'textChannels', - VOICE_CHANNEL = 'voiceChannel', - VOICE_CHANNELS = 'voiceChannels', - CATEGORY_CHANNEL = 'categoryChannel', - CATEGORY_CHANNELS = 'categoryChannels', - NEWS_CHANNEL = 'newsChannel', - NEWS_CHANNELS = 'newsChannels', - STORE_CHANNEL = 'storeChannel', - STORE_CHANNELS = 'storeChannels', - STAGE_CHANNEL = 'stageChannel', - STAGE_CHANNELS = 'stageChannels', - THREAD_CHANNEL = 'threadChannel', - THREAD_CHANNELS = 'threadChannels', - ROLE = 'role', - ROLES = 'roles', - EMOJI = 'emoji', - EMOJIS = 'emojis', - GUILD = 'guild', - GUILDS = 'guilds', - MESSAGE = 'message', - GUILD_MESSAGE = 'guildMessage', - RELEVANT_MESSAGE = 'relevantMessage', - INVITE = 'invite', - USER_MENTION = 'userMention', - MEMBER_MENTION = 'memberMention', - CHANNEL_MENTION = 'channelMention', - ROLE_MENTION = 'roleMention', - EMOJI_MENTION = 'emojiMention', - COMMAND_ALIAS = 'commandAlias', - COMMAND = 'command', - INHIBITOR = 'inhibitor', - LISTENER = 'listener', - TASK = 'task', - CONTEXT_MENU_COMMAND = 'contextMenuCommand', - DURATION = 'duration', - CONTENT_WITH_DURATION = 'contentWithDuration', - PERMISSION = 'permission', - SNOWFLAKE = 'snowflake', - DISCORD_EMOJI = 'discordEmoji', - ROLE_WITH_DURATION = 'roleWithDuration', - ABBREVIATED_NUMBER = 'abbreviatedNumber', - GLOBAL_USER = 'globalUser' -} - -export enum BushBlockedReasonsEnum { - CLIENT = 'client', - BOT = 'bot', - OWNER = 'owner', - SUPER_USER = 'superUser', - GUILD = 'guild', - DM = 'dm', - AUTHOR_NOT_FOUND = 'authorNotFound', - NOT_NSFW = 'notNsfw', - DISABLED_GUILD = 'disabledGuild', - DISABLED_GLOBAL = 'disabledGlobal', - ROLE_BLACKLIST = 'roleBlacklist', - USER_GUILD_BLACKLIST = 'userGuildBlacklist', - USER_GLOBAL_BLACKLIST = 'userGlobalBlacklist', - RESTRICTED_GUILD = 'restrictedGuild', - CHANNEL_GUILD_BLACKLIST = 'channelGuildBlacklist', - CHANNEL_GLOBAL_BLACKLIST = 'channelGlobalBlacklist', - RESTRICTED_CHANNEL = 'restrictedChannel' -} - -export enum BushCommandHandlerEventsEnum { - COMMAND_BLOCKED = 'commandBlocked', - COMMAND_BREAKOUT = 'commandBreakout', - COMMAND_CANCELLED = 'commandCancelled', - COMMAND_FINISHED = 'commandFinished', - COMMAND_INVALID = 'commandInvalid', - COMMAND_LOCKED = 'commandLocked', - COMMAND_STARTED = 'commandStarted', - COOLDOWN = 'cooldown', - ERROR = 'error', - IN_PROMPT = 'inPrompt', - MESSAGE_BLOCKED = 'messageBlocked', - MESSAGE_INVALID = 'messageInvalid', - MISSING_PERMISSIONS = 'missingPermissions', - SLASH_BLOCKED = 'slashBlocked', - SLASH_ERROR = 'slashError', - SLASH_FINISHED = 'slashFinished', - SLASH_MISSING_PERMISSIONS = 'slashMissingPermissions', - SLASH_NOT_FOUND = 'slashNotFound', - SLASH_STARTED = 'slashStarted', - SLASH_ONLY = 'slashOnly' -} - -interface bushColors { - default: '#1FD8F1'; - error: '#EF4947'; - warn: '#FEBA12'; - success: '#3BB681'; - info: '#3B78FF'; - red: '#ff0000'; - blue: '#0055ff'; - aqua: '#00bbff'; - purple: '#8400ff'; - blurple: '#5440cd'; - newBlurple: '#5865f2'; - pink: '#ff00e6'; - green: '#00ff1e'; - darkGreen: '#008f11'; - gold: '#b59400'; - yellow: '#ffff00'; - white: '#ffffff'; - gray: '#a6a6a6'; - lightGray: '#cfcfcf'; - darkGray: '#7a7a7a'; - black: '#000000'; - orange: '#E86100'; - discord: ConstantsColors; + ] as const); } -export type PronounCode = keyof typeof PronounMap; -export type Pronoun = PronounMap; +export type PronounCode = keyof typeof BushConstants['pronounMapping']; +export type Pronoun = typeof BushConstants['pronounMapping'][PronounCode]; diff --git a/src/lib/utils/BushLogger.ts b/src/lib/utils/BushLogger.ts index 4b89622..3421985 100644 --- a/src/lib/utils/BushLogger.ts +++ b/src/lib/utils/BushLogger.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; -import { MessageEmbed, Util, type Message } from 'discord.js'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { MessageEmbed, Util, type Message, type PartialTextBasedChannelFields } from 'discord.js'; import { inspect } from 'util'; import { type BushSendMessageType } from '../extensions/discord-akairo/BushClient'; @@ -52,22 +53,27 @@ export class BushLogger { /** * Logs information. Highlight information by surrounding it in `<<>>`. - * @param header - The header displayed before the content, displayed in cyan. - * @param content - The content to log, highlights displayed in bright blue. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header displayed before the content, displayed in cyan. + * @param content The content to log, highlights displayed in bright blue. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static get log() { return BushLogger.info; } - /** Sends a message to the log channel */ + /** + * Sends a message to the log channel + * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send} + */ public static async channelLog(message: BushSendMessageType) { const channel = await util.getConfigChannel('log'); await channel.send(message).catch(() => {}); } - /** Sends a message to the error channel */ + /** + * Sends a message to the error channel + */ public static async channelError(message: BushSendMessageType): Promise<Message> { const channel = await util.getConfigChannel('error'); return await channel.send(message); @@ -75,8 +81,8 @@ export class BushLogger { /** * Logs debug information. Only works in dev is enabled in the config. - * @param content - The content to log. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param content The content to log. + * @param depth The depth the content will inspected. Defaults to 0. */ public static debug(content: any, depth = 0): void { if (!client.config.isDevelopment) return; @@ -86,7 +92,7 @@ export class BushLogger { /** * Logs raw debug information. Only works in dev is enabled in the config. - * @param content - The content to log. + * @param content The content to log. */ public static debugRaw(...content: any): void { if (!client.config.isDevelopment) return; @@ -95,10 +101,10 @@ export class BushLogger { /** * Logs verbose information. Highlight information by surrounding it in `<<>>`. - * @param header - The header printed before the content, displayed in grey. - * @param content - The content to log, highlights displayed in bright black. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header printed before the content, displayed in grey. + * @param content The content to log, highlights displayed in bright black. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static async verbose(header: string, content: any, sendChannel = false, depth = 0) { if (!client.config.logging.verbose) return; @@ -116,10 +122,10 @@ export class BushLogger { /** * Logs information. Highlight information by surrounding it in `<<>>`. - * @param header - The header displayed before the content, displayed in cyan. - * @param content - The content to log, highlights displayed in bright blue. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header displayed before the content, displayed in cyan. + * @param content The content to log, highlights displayed in bright blue. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static async info(header: string, content: any, sendChannel = true, depth = 0) { if (!client.config.logging.info) return; @@ -137,10 +143,10 @@ export class BushLogger { /** * Logs warnings. Highlight information by surrounding it in `<<>>`. - * @param header - The header displayed before the content, displayed in yellow. - * @param content - The content to log, highlights displayed in bright yellow. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header displayed before the content, displayed in yellow. + * @param content The content to log, highlights displayed in bright yellow. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static async warn(header: string, content: any, sendChannel = true, depth = 0) { const newContent = this.#inspectContent(content, depth, true); @@ -161,10 +167,10 @@ export class BushLogger { /** * Logs errors. Highlight information by surrounding it in `<<>>`. - * @param header - The header displayed before the content, displayed in bright red. - * @param content - The content to log, highlights displayed in bright red. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header displayed before the content, displayed in bright red. + * @param content The content to log, highlights displayed in bright red. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static async error(header: string, content: any, sendChannel = true, depth = 0) { const newContent = this.#inspectContent(content, depth, true); @@ -185,10 +191,10 @@ export class BushLogger { /** * Logs successes. Highlight information by surrounding it in `<<>>`. - * @param header - The header displayed before the content, displayed in green. - * @param content - The content to log, highlights displayed in bright green. - * @param sendChannel - Should this also be logged to discord? Defaults to false. - * @param depth - The depth the content will inspected. Defaults to 0. + * @param header The header displayed before the content, displayed in green. + * @param content The content to log, highlights displayed in bright green. + * @param sendChannel Should this also be logged to discord? Defaults to false. + * @param depth The depth the content will inspected. Defaults to 0. */ public static async success(header: string, content: any, sendChannel = true, depth = 0) { const newContent = this.#inspectContent(content, depth, true); @@ -206,3 +212,5 @@ export class BushLogger { await this.channelLog({ embeds: [embed] }).catch(() => {}); } } + +/** @typedef {PartialTextBasedChannelFields} vscodeDontDeleteMyImportTy */
\ No newline at end of file |