aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-11-28 09:27:41 -0500
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2021-11-28 09:27:41 -0500
commit453683b57b8ff013ff25e2aaa4aa1d2e047edcb7 (patch)
tree8b98d2f30dbb6a8448602446cfacf9091667cc33 /src/lib
parentde4c3dcaf172804d34ae708be1ed3e75af42f4d5 (diff)
downloadtanzanite-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.ts52
-rw-r--r--src/lib/common/util/Arg.ts6
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts283
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts309
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts48
-rw-r--r--src/lib/extensions/global.d.ts9
-rw-r--r--src/lib/utils/BushConstants.ts399
-rw-r--r--src/lib/utils/BushLogger.ts68
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