aboutsummaryrefslogtreecommitdiff
path: root/src/lib/utils
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-08-18 22:42:12 -0400
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-08-18 22:42:12 -0400
commit2356d2c44736fb83021dacb551625852111c8ce6 (patch)
tree10408d22fdd7a358d2f5c5917c3b59e55aa4c19d /src/lib/utils
parent8aed6f93f7740c592cbc0e2f9fd3269c05286077 (diff)
downloadtanzanite-2356d2c44736fb83021dacb551625852111c8ce6.tar.gz
tanzanite-2356d2c44736fb83021dacb551625852111c8ce6.tar.bz2
tanzanite-2356d2c44736fb83021dacb551625852111c8ce6.zip
restructure, experimental presence and member automod, fixed bugs probably made some more bugs
Diffstat (limited to 'src/lib/utils')
-rw-r--r--src/lib/utils/AllowedMentions.ts68
-rw-r--r--src/lib/utils/BushCache.ts26
-rw-r--r--src/lib/utils/BushClientUtils.ts498
-rw-r--r--src/lib/utils/BushConstants.ts531
-rw-r--r--src/lib/utils/BushLogger.ts315
-rw-r--r--src/lib/utils/BushUtils.ts612
-rw-r--r--src/lib/utils/CanvasProgressBar.ts83
7 files changed, 0 insertions, 2133 deletions
diff --git a/src/lib/utils/AllowedMentions.ts b/src/lib/utils/AllowedMentions.ts
deleted file mode 100644
index d2eb030..0000000
--- a/src/lib/utils/AllowedMentions.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { type MessageMentionOptions, type MessageMentionTypes } from 'discord.js';
-
-/**
- * A utility class for creating allowed mentions.
- */
-export class AllowedMentions {
- /**
- * @param everyone Whether everyone and here should be mentioned.
- * @param roles Whether roles should be mentioned.
- * @param users Whether users should be mentioned.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public constructor(public everyone = false, public roles = false, public users = true, public repliedUser = true) {}
-
- /**
- * Don't mention anyone.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static none(repliedUser = true): MessageMentionOptions {
- return { parse: [], repliedUser };
- }
-
- /**
- * Mention @everyone and @here, roles, and users.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static all(repliedUser = true): MessageMentionOptions {
- return { parse: ['everyone', 'roles', 'users'], repliedUser };
- }
-
- /**
- * Mention users.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static users(repliedUser = true): MessageMentionOptions {
- return { parse: ['users'], repliedUser };
- }
-
- /**
- * Mention everyone and here.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static everyone(repliedUser = true): MessageMentionOptions {
- return { parse: ['everyone'], repliedUser };
- }
-
- /**
- * Mention roles.
- * @param repliedUser Whether the author of the Message being replied to should be mentioned.
- */
- public static roles(repliedUser = true): MessageMentionOptions {
- return { parse: ['roles'], repliedUser };
- }
-
- /**
- * Converts this into a MessageMentionOptions object.
- */
- public toObject(): MessageMentionOptions {
- return {
- parse: [
- ...(this.users ? ['users'] : []),
- ...(this.roles ? ['roles'] : []),
- ...(this.everyone ? ['everyone'] : [])
- ] as MessageMentionTypes[],
- repliedUser: this.repliedUser
- };
- }
-}
diff --git a/src/lib/utils/BushCache.ts b/src/lib/utils/BushCache.ts
deleted file mode 100644
index 22a13ef..0000000
--- a/src/lib/utils/BushCache.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { BadWords, GlobalModel, SharedModel, type Guild } from '#lib';
-import { Collection, type Snowflake } from 'discord.js';
-
-export class BushCache {
- public global = new GlobalCache();
- public shared = new SharedCache();
- public guilds = new GuildCache();
-}
-
-export class GlobalCache implements Omit<GlobalModel, 'environment'> {
- public disabledCommands: string[] = [];
- public blacklistedChannels: Snowflake[] = [];
- public blacklistedGuilds: Snowflake[] = [];
- public blacklistedUsers: Snowflake[] = [];
-}
-
-export class SharedCache implements Omit<SharedModel, 'primaryKey'> {
- public superUsers: Snowflake[] = [];
- public privilegedUsers: Snowflake[] = [];
- public badLinksSecret: string[] = [];
- public badLinks: string[] = [];
- public badWords: BadWords = {};
- public autoBanCode: string | null = null;
-}
-
-export class GuildCache extends Collection<Snowflake, Guild> {}
diff --git a/src/lib/utils/BushClientUtils.ts b/src/lib/utils/BushClientUtils.ts
deleted file mode 100644
index 920ff40..0000000
--- a/src/lib/utils/BushClientUtils.ts
+++ /dev/null
@@ -1,498 +0,0 @@
-import assert from 'assert/strict';
-import {
- cleanCodeBlockContent,
- DMChannel,
- escapeCodeBlock,
- GuildMember,
- Message,
- PartialDMChannel,
- Routes,
- TextBasedChannel,
- ThreadMember,
- User,
- type APIMessage,
- type Client,
- type Snowflake,
- type UserResolvable
-} from 'discord.js';
-import got from 'got';
-import _ from 'lodash';
-import { ConfigChannelKey } from '../../../config/Config.js';
-import CommandErrorListener from '../../listeners/commands/commandError.js';
-import { BushInspectOptions } from '../common/typings/BushInspectOptions.js';
-import { CodeBlockLang } from '../common/typings/CodeBlockLang.js';
-import { CommandMessage } from '../extensions/discord-akairo/BushCommand.js';
-import { SlashMessage } from '../extensions/discord-akairo/SlashMessage.js';
-import { Global } from '../models/shared/Global.js';
-import { Shared } from '../models/shared/Shared.js';
-import { GlobalCache, SharedCache } from './BushCache.js';
-import { emojis, Pronoun, PronounCode, pronounMapping, regex } from './BushConstants.js';
-import { addOrRemoveFromArray, formatError, inspect } from './BushUtils.js';
-
-/**
- * Utilities that require access to the client.
- */
-export class BushClientUtils {
- /**
- * The hastebin urls used to post to hastebin, attempts to post in order
- */
- #hasteURLs: string[] = [
- 'https://hst.sh',
- // 'https://hasteb.in',
- 'https://hastebin.com',
- 'https://mystb.in',
- 'https://haste.clicksminuteper.net',
- 'https://paste.pythondiscord.com',
- 'https://haste.unbelievaboat.com'
- // 'https://haste.tyman.tech'
- ];
-
- public constructor(private readonly client: Client) {}
-
- /**
- * Maps an array of user ids to user objects.
- * @param ids The list of IDs to map
- * @returns The list of users mapped
- */
- public async mapIDs(ids: Snowflake[]): Promise<User[]> {
- return await Promise.all(ids.map((id) => this.client.users.fetch(id)));
- }
-
- /**
- * Posts text to hastebin
- * @param content The text to post
- * @returns The url of the posted text
- */
- 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()})`));
- return { error: 'content too long' };
- } else if (content.length > 400_000) {
- content = content.substring(0, 400_000);
- isSubstr = true;
- }
- for (const url of this.#hasteURLs) {
- try {
- const res: HastebinRes = await got.post(`${url}/documents`, { body: content }).json();
- return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined };
- } catch {
- void this.client.console.error('haste', `Unable to upload haste to ${url}`);
- }
- }
- return { error: 'unable to post' };
- }
-
- /**
- * Resolves a user-provided string into a user object, if possible
- * @param text The text to try and resolve
- * @returns The user resolved or null
- */
- public async resolveUserAsync(text: string): Promise<User | null> {
- const idReg = /\d{17,19}/;
- const idMatch = text.match(idReg);
- if (idMatch) {
- try {
- return await this.client.users.fetch(text as Snowflake);
- } catch {}
- }
- const mentionReg = /<@!?(?<id>\d{17,19})>/;
- const mentionMatch = text.match(mentionReg);
- if (mentionMatch) {
- try {
- return await this.client.users.fetch(mentionMatch.groups!.id as Snowflake);
- } catch {}
- }
- const user = this.client.users.cache.find((u) => u.username === text);
- if (user) return user;
- return null;
- }
-
- /**
- * 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 = '';
- code = escapeCodeBlock(code);
- const prefix = `\`\`\`${language}\n`;
- const suffix = '\n```';
- if (code.length + (prefix + suffix).length >= length) {
- const haste_ = await this.haste(code, substr);
- hasteOut = `Too large to display. ${
- haste_.url
- ? `Hastebin: ${haste_.url}${language ? `.${language}` : ''}${haste_.error ? ` - ${haste_.error}` : ''}`
- : `${emojis.error} Hastebin: ${haste_.error}`
- }`;
- }
-
- const FormattedHaste = hasteOut.length ? `\n${hasteOut}` : '';
- const shortenedCode = hasteOut ? code.substring(0, length - (prefix + FormattedHaste + suffix).length) : code;
- const code3 = code.length ? prefix + shortenedCode + suffix + FormattedHaste : prefix + suffix;
- if (code3.length > length) {
- void this.client.console.warn(`codeblockError`, `Required Length: ${length}. Actual Length: ${code3.length}`, true);
- void this.client.console.warn(`codeblockError`, code3, true);
- throw new Error('code too long');
- }
- return code3;
- }
-
- /**
- * 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 {
- return (
- {
- token: 'Main Token',
- devToken: 'Dev Token',
- betaToken: 'Beta Token',
- hypixelApiKey: 'Hypixel Api Key',
- wolframAlphaAppId: 'Wolfram|Alpha App ID',
- dbPassword: 'Database Password'
- }[key] ?? key
- );
- }
-
- /**
- * 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 { ...this.client.config.credentials, dbPassword: this.client.config.db.password }) {
- const credential = { ...this.client.config.credentials, dbPassword: this.client.config.db.password }[
- credentialName as keyof typeof this.client.config.credentials
- ];
- if (credential === null || credential === '') continue;
- const replacement = this.#mapCredential(credentialName);
- const escapeRegex = /[.*+?^${}()|[\]\\]/g;
- text = text.replace(new RegExp(credential.toString().replace(escapeRegex, '\\$&'), 'g'), `[${replacement} Omitted]`);
- text = text.replace(
- new RegExp([...credential.toString()].reverse().join('').replace(escapeRegex, '\\$&'), 'g'),
- `[${replacement} Omitted]`
- );
- }
- return text;
- }
-
- /**
- * 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,
- language?: CodeBlockLang | '',
- inspectOptions?: BushInspectOptions,
- length = 1024
- ) {
- input = inspect(input, inspectOptions ?? undefined);
- if (inspectOptions) inspectOptions.inspectStrings = undefined;
- input = cleanCodeBlockContent(input);
- input = this.redact(input);
- return this.codeblock(input, length, language, true);
- }
-
- /**
- * 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 = inspect(input, inspectOptions ?? undefined);
- input = this.redact(input);
- return this.haste(input, true);
- }
-
- /**
- * 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 = inspect(input, inspectOptions ?? undefined);
- return this.redact(input);
- }
-
- /**
- * Get the global cache.
- */
- public getGlobal(): GlobalCache;
- /**
- * Get a key from the global cache.
- * @param key The key to get in the global cache.
- */
- public getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K];
- public getGlobal(key?: keyof GlobalCache) {
- return key ? this.client.cache.global[key] : this.client.cache.global;
- }
-
- /**
- * Get the shared cache.
- */
- public getShared(): SharedCache;
- /**
- * Get a key from the shared cache.
- * @param key The key to get in the shared cache.
- */
- public getShared<K extends keyof SharedCache>(key: K): SharedCache[K];
- public getShared(key?: keyof SharedCache) {
- return key ? this.client.cache.shared[key] : this.client.cache.shared;
- }
-
- /**
- * 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 Client['cache']['global']>(
- action: 'add' | 'remove',
- key: K,
- value: Client['cache']['global'][K][0]
- ): Promise<Global | void> {
- const row =
- (await Global.findByPk(this.client.config.environment)) ??
- (await Global.create({ environment: this.client.config.environment }));
- const oldValue: any[] = row[key];
- const newValue = addOrRemoveFromArray(action, oldValue, value);
- row[key] = newValue;
- this.client.cache.global[key] = newValue;
- return await row.save().catch((e) => this.handleError('insertOrRemoveFromGlobal', e));
- }
-
- /**
- * Add or remove an element from an array stored in the Shared database.
- * @param action Either `add` or `remove` an element.
- * @param key The key of the element in the shared cache to update.
- * @param value The value to add/remove from the array.
- */
- public async insertOrRemoveFromShared<K extends Exclude<keyof Client['cache']['shared'], 'badWords' | 'autoBanCode'>>(
- action: 'add' | 'remove',
- key: K,
- value: Client['cache']['shared'][K][0]
- ): Promise<Shared | void> {
- const row = (await Shared.findByPk(0)) ?? (await Shared.create());
- const oldValue: any[] = row[key];
- const newValue = addOrRemoveFromArray(action, oldValue, value);
- row[key] = newValue;
- this.client.cache.shared[key] = newValue;
- return await row.save().catch((e) => this.handleError('insertOrRemoveFromShared', e));
- }
-
- /**
- * 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 Client['cache']['global']>(
- key: K,
- value: Client['cache']['global'][K]
- ): Promise<Global | void> {
- const row =
- (await Global.findByPk(this.client.config.environment)) ??
- (await Global.create({ environment: this.client.config.environment }));
- row[key] = value;
- this.client.cache.global[key] = value;
- return await row.save().catch((e) => this.handleError('setGlobal', e));
- }
-
- /**
- * Updates an element in the Shared database.
- * @param key The key in the shared cache to update.
- * @param value The value to set the key to.
- */
- public async setShared<K extends Exclude<keyof Client['cache']['shared'], 'badWords' | 'autoBanCode'>>(
- key: K,
- value: Client['cache']['shared'][K]
- ): Promise<Shared | void> {
- const row = (await Shared.findByPk(0)) ?? (await Shared.create());
- row[key] = value;
- this.client.cache.shared[key] = value;
- return await row.save().catch((e) => this.handleError('setShared', e));
- }
-
- /**
- * 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 this.client.console.error(_.camelCase(context), `An error occurred:\n${formatError(error, false)}`, false);
- await this.client.console.channelError({
- embeds: await CommandErrorListener.generateErrorEmbed(this.client, { type: 'unhandledRejection', error: error, context })
- });
- }
-
- /**
- * Fetches a user from discord.
- * @param user The user to fetch
- * @returns Undefined if the user is not found, otherwise the user.
- */
- public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<User | undefined> {
- if (user == null) return undefined;
- const resolvedUser =
- user instanceof User
- ? user
- : user instanceof GuildMember
- ? user.user
- : user instanceof ThreadMember
- ? user.user
- : user instanceof Message
- ? user.author
- : undefined;
-
- return resolvedUser ?? (await this.client.users.fetch(user as Snowflake).catch(() => undefined));
- }
-
- /**
- * Get the pronouns of a discord user from pronoundb.org
- * @param user The user to retrieve the promises of.
- * @returns The human readable pronouns of the user, or undefined if they do not have any.
- */
- public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
- const _user = await this.resolveNonCachedUser(user);
- if (!_user) throw new Error(`Cannot find user ${user}`);
- const apiRes = (await got
- .get(`https://pronoundb.org/api/v1/lookup?platform=discord&id=${_user.id}`)
- .json()
- .catch(() => undefined)) as { pronouns: PronounCode } | undefined;
-
- if (!apiRes) return undefined;
- assert(apiRes.pronouns);
-
- return pronounMapping[apiRes.pronouns!]!;
- }
-
- /**
- * Uploads an image to imgur.
- * @param image The image to upload.
- * @returns The url of the imgur.
- */
- public async uploadImageToImgur(image: string) {
- const clientId = this.client.config.credentials.imgurClientId;
-
- const resp = (await got
- .post('https://api.imgur.com/3/upload', {
- headers: {
- Authorization: `Client-ID ${clientId}`,
- Accept: 'application/json'
- },
- form: {
- image: image,
- type: 'base64'
- },
- followRedirect: true
- })
- .json()) as { data: { link: string } };
-
- return resp.data.link;
- }
-
- /**
- * Gets the prefix based off of the message.
- * @param message The message to get the prefix from.
- * @returns The prefix.
- */
- public prefix(message: CommandMessage | SlashMessage): string {
- return message.util.isSlash
- ? '/'
- : this.client.config.isDevelopment
- ? 'dev '
- : message.util.parsed?.prefix ?? this.client.config.prefix;
- }
-
- public async resolveMessageLinks(content: string | null): Promise<MessageLinkParts[]> {
- const res: MessageLinkParts[] = [];
-
- if (!content) return res;
-
- const regex_ = new RegExp(regex.messageLink);
- let match: RegExpExecArray | null;
- while (((match = regex_.exec(content)), match !== null)) {
- const input = match.input;
- if (!match.groups || !input) continue;
- if (input.startsWith('<') && input.endsWith('>')) continue;
-
- const { guild_id, channel_id, message_id } = match.groups;
- if (!guild_id || !channel_id || !message_id) continue;
-
- res.push({ guild_id, channel_id, message_id });
- }
-
- return res;
- }
-
- public async resolveMessagesFromLinks(content: string): Promise<APIMessage[]> {
- const res: APIMessage[] = [];
-
- const links = await this.resolveMessageLinks(content);
- if (!links.length) return [];
-
- for (const { guild_id, channel_id, message_id } of links) {
- const guild = this.client.guilds.cache.get(guild_id);
- if (!guild) continue;
- const channel = guild.channels.cache.get(channel_id);
- if (!channel || (!channel.isTextBased() && !channel.isThread())) continue;
-
- const message = (await this.client.rest
- .get(Routes.channelMessage(channel_id, message_id))
- .catch(() => null)) as APIMessage | null;
- if (!message) continue;
-
- res.push(message);
- }
-
- return res;
- }
-
- /**
- * Resolves a channel from the config and ensures it is a non-dm-based-text-channel.
- * @param channel The channel to retrieve.
- */
- public async getConfigChannel(
- channel: ConfigChannelKey
- ): Promise<Exclude<TextBasedChannel, DMChannel | PartialDMChannel> | null> {
- const channels = this.client.config.channels;
- if (!(channel in channels))
- throw new TypeError(`Invalid channel provided (${channel}), must be one of ${Object.keys(channels).join(' ')}`);
-
- const channelId = channels[channel];
- if (channelId === '') return null;
-
- const res = await this.client.channels.fetch(channelId);
-
- if (!res?.isTextBased() || res.isDMBased()) return null;
-
- return res;
- }
-}
-
-interface HastebinRes {
- key: string;
-}
-
-export interface HasteResults {
- url?: string;
- error?: 'content too long' | 'substr' | 'unable to post';
-}
-
-export interface MessageLinkParts {
- guild_id: Snowflake;
- channel_id: Snowflake;
- message_id: Snowflake;
-}
diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts
deleted file mode 100644
index 090616c..0000000
--- a/src/lib/utils/BushConstants.ts
+++ /dev/null
@@ -1,531 +0,0 @@
-import deepLock from 'deep-lock';
-import {
- ArgumentMatches as AkairoArgumentMatches,
- ArgumentTypes as AkairoArgumentTypes,
- BuiltInReasons,
- CommandHandlerEvents as AkairoCommandHandlerEvents
-} from 'discord-akairo/dist/src/util/Constants.js';
-import { Colors, GuildFeature } from 'discord.js';
-
-const rawCapeUrl = 'https://raw.githubusercontent.com/NotEnoughUpdates/capes/master/';
-
-/**
- * Time units in milliseconds
- */
-export const enum Time {
- /**
- * One millisecond (1 ms).
- */
- Millisecond = 1,
-
- /**
- * One second (1,000 ms).
- */
- Second = Millisecond * 1000,
-
- /**
- * One minute (60,000 ms).
- */
- Minute = Second * 60,
-
- /**
- * One hour (3,600,000 ms).
- */
- Hour = Minute * 60,
-
- /**
- * One day (86,400,000 ms).
- */
- Day = Hour * 24,
-
- /**
- * One week (604,800,000 ms).
- */
- Week = Day * 7,
-
- /**
- * One month (2,629,800,000 ms).
- */
- Month = Day * 30.4375, // average of days in a month (including leap years)
-
- /**
- * One year (31,557,600,000 ms).
- */
- Year = Day * 365.25 // average with leap years
-}
-
-export const emojis = Object.freeze({
- 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);
-
-export const emojisRaw = Object.freeze({
- success: '837109864101707807',
- warn: '848726900876247050',
- error: '837123021016924261',
- successFull: '850118767576088646',
- warnFull: '850118767391539312',
- errorFull: '850118767295201350',
- mad: '783046135392239626',
- join: '850198029809614858',
- leave: '850198048205307919',
- loading: '853419254619963392',
- offlineCircle: '787550565382750239',
- dndCircle: '787550487633330176',
- idleCircle: '787550520956551218',
- onlineCircle: '787550449435803658',
- cross: '878319362539421777',
- check: '878320135297961995'
-} as const);
-
-export const colors = Object.freeze({
- default: 0x1fd8f1,
- error: 0xef4947,
- warn: 0xfeba12,
- success: 0x3bb681,
- info: 0x3b78ff,
- red: 0xff0000,
- blue: 0x0055ff,
- aqua: 0x00bbff,
- purple: 0x8400ff,
- blurple: 0x5440cd,
- newBlurple: 0x5865f2,
- pink: 0xff00e6,
- green: 0x00ff1e,
- darkGreen: 0x008f11,
- gold: 0xb59400,
- yellow: 0xffff00,
- white: 0xffffff,
- gray: 0xa6a6a6,
- lightGray: 0xcfcfcf,
- darkGray: 0x7a7a7a,
- black: 0x000000,
- orange: 0xe86100,
- ...Colors
-} as const);
-
-// Somewhat stolen from @Mzato0001
-export const timeUnits = deepLock({
- milliseconds: {
- match: / (?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im,
- value: Time.Millisecond
- },
- seconds: {
- match: / (?:(?<seconds>-?(?:\d+)?\.?\d+) *(?:seconds?|secs?|s))/im,
- value: Time.Second
- },
- minutes: {
- match: / (?:(?<minutes>-?(?:\d+)?\.?\d+) *(?:minutes?|mins?|m))/im,
- value: Time.Minute
- },
- hours: {
- match: / (?:(?<hours>-?(?:\d+)?\.?\d+) *(?:hours?|hrs?|h))/im,
- value: Time.Hour
- },
- days: {
- match: / (?:(?<days>-?(?:\d+)?\.?\d+) *(?:days?|d))/im,
- value: Time.Day
- },
- weeks: {
- match: / (?:(?<weeks>-?(?:\d+)?\.?\d+) *(?:weeks?|w))/im,
- value: Time.Week
- },
- months: {
- match: / (?:(?<months>-?(?:\d+)?\.?\d+) *(?:months?|mon|mo))/im,
- value: Time.Month
- },
- years: {
- match: / (?:(?<years>-?(?:\d+)?\.?\d+) *(?:years?|y))/im,
- value: Time.Year
- }
-} as const);
-
-export const regex = deepLock({
- snowflake: /^\d{15,21}$/im,
-
- discordEmoji: /<a?:(?<name>[a-zA-Z0-9_]+):(?<id>\d{15,21})>/im,
-
- /*
- * Taken with permission from Geek:
- * https://github.com/FireDiscordBot/bot/blob/5d1990e5f8b52fcc72261d786aa3c7c7c65ab5e8/lib/util/constants.ts#L276
- */
- /** **This has the global flag, make sure to handle it correctly.** */
- messageLink:
- /<?https:\/\/(?:ptb\.|canary\.|staging\.)?discord(?:app)?\.com?\/channels\/(?<guild_id>\d{15,21})\/(?<channel_id>\d{15,21})\/(?<message_id>\d{15,21})>?/gim
-} as const);
-
-/**
- * Maps the response from pronoundb.org to a readable format
- */
-export const pronounMapping = Object.freeze({
- 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
- */
-export const mappings = deepLock({
- guilds: {
- "Moulberry's Bush": '516977525906341928',
- "Moulberry's Tree": '767448775450820639',
- 'MB Staff': '784597260465995796',
- "IRONM00N's Space Ship": '717176538717749358'
- },
-
- channels: {
- 'neu-support': '714332750156660756',
- 'giveaways': '767782084981817344'
- },
-
- users: {
- IRONM00N: '322862723090219008',
- Moulberry: '211288288055525376',
- nopo: '384620942577369088',
- Bestower: '496409778822709251'
- },
-
- permissions: {
- CreateInstantInvite: { name: 'Create Invite', important: false },
- KickMembers: { name: 'Kick Members', important: true },
- BanMembers: { name: 'Ban Members', important: true },
- Administrator: { name: 'Administrator', important: true },
- ManageChannels: { name: 'Manage Channels', important: true },
- ManageGuild: { name: 'Manage Server', important: true },
- AddReactions: { name: 'Add Reactions', important: false },
- ViewAuditLog: { name: 'View Audit Log', important: true },
- PrioritySpeaker: { name: 'Priority Speaker', important: true },
- Stream: { name: 'Video', important: false },
- ViewChannel: { name: 'View Channel', important: false },
- SendMessages: { name: 'Send Messages', important: false },
- SendTTSMessages: { name: 'Send Text-to-Speech Messages', important: true },
- ManageMessages: { name: 'Manage Messages', important: true },
- EmbedLinks: { name: 'Embed Links', important: false },
- AttachFiles: { name: 'Attach Files', important: false },
- ReadMessageHistory: { name: 'Read Message History', important: false },
- MentionEveryone: { name: 'Mention @\u200Beveryone, @\u200Bhere, and All Roles', important: true }, // name has a zero-width space to prevent accidents
- UseExternalEmojis: { name: 'Use External Emoji', important: false },
- ViewGuildInsights: { name: 'View Server Insights', important: true },
- Connect: { name: 'Connect', important: false },
- Speak: { name: 'Speak', important: false },
- MuteMembers: { name: 'Mute Members', important: true },
- DeafenMembers: { name: 'Deafen Members', important: true },
- MoveMembers: { name: 'Move Members', important: true },
- UseVAD: { name: 'Use Voice Activity', important: false },
- ChangeNickname: { name: 'Change Nickname', important: false },
- ManageNicknames: { name: 'Change Nicknames', important: true },
- ManageRoles: { name: 'Manage Roles', important: true },
- ManageWebhooks: { name: 'Manage Webhooks', important: true },
- ManageEmojisAndStickers: { name: 'Manage Emojis and Stickers', important: true },
- UseApplicationCommands: { name: 'Use Slash Commands', important: false },
- RequestToSpeak: { name: 'Request to Speak', important: false },
- ManageEvents: { name: 'Manage Events', important: true },
- ManageThreads: { name: 'Manage Threads', important: true },
- CreatePublicThreads: { name: 'Create Public Threads', important: false },
- CreatePrivateThreads: { name: 'Create Private Threads', important: false },
- UseExternalStickers: { name: 'Use External Stickers', important: false },
- SendMessagesInThreads: { name: 'Send Messages In Threads', important: false },
- StartEmbeddedActivities: { name: 'Start Activities', important: false },
- ModerateMembers: { name: 'Timeout Members', important: true },
- UseEmbeddedActivities: { name: 'Use Activities', important: false }
- },
-
- // prettier-ignore
- features: {
- [GuildFeature.Verified]: { name: 'Verified', important: true, emoji: '<:verified:850795049817473066>', weight: 0 },
- [GuildFeature.Partnered]: { name: 'Partnered', important: true, emoji: '<:partneredServer:850794851955507240>', weight: 1 },
- [GuildFeature.MoreStickers]: { name: 'More Stickers', important: true, emoji: null, weight: 2 },
- MORE_EMOJIS: { name: 'More Emoji', important: true, emoji: '<:moreEmoji:850786853497602080>', weight: 3 },
- [GuildFeature.Featurable]: { name: 'Featurable', important: true, emoji: '<:featurable:850786776372084756>', weight: 4 },
- [GuildFeature.RelayEnabled]: { name: 'Relay Enabled', important: true, emoji: '<:relayEnabled:850790531441229834>', weight: 5 },
- [GuildFeature.Discoverable]: { name: 'Discoverable', important: true, emoji: '<:discoverable:850786735360966656>', weight: 6 },
- ENABLED_DISCOVERABLE_BEFORE: { name: 'Enabled Discovery Before', important: false, emoji: '<:enabledDiscoverableBefore:850786754670624828>', weight: 7 },
- [GuildFeature.MonetizationEnabled]: { name: 'Monetization Enabled', important: true, emoji: null, weight: 8 },
- [GuildFeature.TicketedEventsEnabled]: { name: 'Ticketed Events Enabled', important: true, emoji: null, weight: 9 },
- [GuildFeature.PreviewEnabled]: { name: 'Preview Enabled', important: true, emoji: '<:previewEnabled:850790508266913823>', weight: 10 },
- COMMERCE: { name: 'Store Channels', important: true, emoji: '<:storeChannels:850786692432396338>', weight: 11 },
- [GuildFeature.VanityURL]: { name: 'Vanity URL', important: false, emoji: '<:vanityURL:850790553079644160>', weight: 12 },
- [GuildFeature.VIPRegions]: { name: 'VIP Regions', important: false, emoji: '<:VIPRegions:850794697496854538>', weight: 13 },
- [GuildFeature.AnimatedIcon]: { name: 'Animated Icon', important: false, emoji: '<:animatedIcon:850774498071412746>', weight: 14 },
- [GuildFeature.Banner]: { name: 'Banner', important: false, emoji: '<:banner:850786673150787614>', weight: 15 },
- [GuildFeature.InviteSplash]: { name: 'Invite Splash', important: false, emoji: '<:inviteSplash:850786798246559754>', weight: 16 },
- [GuildFeature.PrivateThreads]: { name: 'Private Threads', important: false, emoji: '<:privateThreads:869763711894700093>', weight: 17 },
- THREE_DAY_THREAD_ARCHIVE: { name: 'Three Day Thread Archive', important: false, emoji: '<:threeDayThreadArchive:869767841652564008>', weight: 19 },
- SEVEN_DAY_THREAD_ARCHIVE: { name: 'Seven Day Thread Archive', important: false, emoji: '<:sevenDayThreadArchive:869767896123998288>', weight: 20 },
- [GuildFeature.RoleIcons]: { name: 'Role Icons', important: false, emoji: '<:roleIcons:876993381929222175>', weight: 21 },
- [GuildFeature.News]: { name: 'Announcement Channels', important: false, emoji: '<:announcementChannels:850790491796013067>', weight: 22 },
- [GuildFeature.MemberVerificationGateEnabled]: { name: 'Membership Verification Gate', important: false, emoji: '<:memberVerificationGateEnabled:850786829984858212>', weight: 23 },
- [GuildFeature.WelcomeScreenEnabled]: { name: 'Welcome Screen Enabled', important: false, emoji: '<:welcomeScreenEnabled:850790575875817504>', weight: 24 },
- [GuildFeature.Community]: { name: 'Community', important: false, emoji: '<:community:850786714271875094>', weight: 25 },
- THREADS_ENABLED: {name: 'Threads Enabled', important: false, emoji: '<:threadsEnabled:869756035345317919>', weight: 26 },
- THREADS_ENABLED_TESTING: {name: 'Threads Enabled Testing', important: false, emoji: null, weight: 27 },
- [GuildFeature.AnimatedBanner]: { name: 'Animated Banner', important: false, emoji: null, weight: 28 },
- [GuildFeature.HasDirectoryEntry]: { name: 'Has Directory Entry', important: true, emoji: null, weight: 29 },
- [GuildFeature.Hub]: { name: 'Hub', important: true, emoji: null, weight: 30 },
- [GuildFeature.LinkedToHub]: { name: 'Linked To Hub', important: true, emoji: null, weight: 31 },
- },
-
- regions: {
- 'automatic': ':united_nations: Automatic',
- 'brazil': ':flag_br: Brazil',
- 'europe': ':flag_eu: Europe',
- 'hongkong': ':flag_hk: Hongkong',
- 'india': ':flag_in: India',
- 'japan': ':flag_jp: Japan',
- 'russia': ':flag_ru: Russia',
- 'singapore': ':flag_sg: Singapore',
- 'southafrica': ':flag_za: South Africa',
- 'sydney': ':flag_au: Sydney',
- 'us-central': ':flag_us: US Central',
- 'us-east': ':flag_us: US East',
- 'us-south': ':flag_us: US South',
- 'us-west': ':flag_us: US West'
- },
-
- otherEmojis: {
- ServerBooster1: '<:serverBooster1:848740052091142145>',
- ServerBooster2: '<:serverBooster2:848740090506510388>',
- ServerBooster3: '<:serverBooster3:848740124992077835>',
- ServerBooster6: '<:serverBooster6:848740155245461514>',
- ServerBooster9: '<:serverBooster9:848740188846030889>',
- ServerBooster12: '<:serverBooster12:848740304365551668>',
- ServerBooster15: '<:serverBooster15:848740354890137680>',
- ServerBooster18: '<:serverBooster18:848740402886606868>',
- ServerBooster24: '<:serverBooster24:848740444628320256>',
- Nitro: '<:nitro:848740498054971432>',
- Booster: '<:booster:848747775020892200>',
- Owner: '<:owner:848746439311753286>',
- Admin: '<:admin:848963914628333598>',
- Superuser: '<:superUser:848947986326224926>',
- Developer: '<:developer:848954538111139871>',
- Bot: '<:bot:1006929813203853427>',
- BushVerified: '<:verfied:853360152090771497>',
- BoostTier1: '<:boostitle:853363736679940127>',
- BoostTier2: '<:boostitle:853363752728789075>',
- BoostTier3: '<:boostitle:853363769132056627>',
- ChannelText: '<:text:853375537791893524>',
- ChannelNews: '<:announcements:853375553531674644>',
- ChannelVoice: '<:voice:853375566735212584>',
- ChannelStage: '<:stage:853375583521210468>',
- // ChannelStore: '<:store:853375601175691266>',
- ChannelCategory: '<:category:853375615260819476>',
- ChannelThread: '<:thread:865033845753249813>'
- },
-
- userFlags: {
- Staff: '<:discordEmployee:848742947826434079>',
- Partner: '<:partneredServerOwner:848743051593777152>',
- Hypesquad: '<:hypeSquadEvents:848743108283072553>',
- BugHunterLevel1: '<:bugHunter:848743239850393640>',
- HypeSquadOnlineHouse1: '<:hypeSquadBravery:848742910563844127>',
- HypeSquadOnlineHouse2: '<:hypeSquadBrilliance:848742840649646101>',
- HypeSquadOnlineHouse3: '<:hypeSquadBalance:848742877537370133>',
- PremiumEarlySupporter: '<:earlySupporter:848741030102171648>',
- TeamPseudoUser: 'TeamPseudoUser',
- BugHunterLevel2: '<:bugHunterGold:848743283080822794>',
- VerifiedBot: '<:verifiedbot_rebrand1:938928232667947028><:verifiedbot_rebrand2:938928355707879475>',
- VerifiedDeveloper: '<:earlyVerifiedBotDeveloper:848741079875846174>',
- CertifiedModerator: '<:discordCertifiedModerator:877224285901582366>',
- BotHTTPInteractions: 'BotHTTPInteractions',
- Spammer: 'Spammer',
- Quarantined: 'Quarantined'
- },
-
- status: {
- online: '<:online:848937141639577690>',
- idle: '<:idle:848937158261211146>',
- dnd: '<:dnd:848937173780135986>',
- offline: '<:offline:848939387277672448>',
- streaming: '<:streaming:848937187479519242>'
- },
-
- maybeNitroDiscrims: ['1111', '2222', '3333', '4444', '5555', '6666', '6969', '7777', '8888', '9999'],
-
- capes: [
- /* supporter capes */
- { name: 'patreon1', purchasable: false /* moulberry no longer offers */ },
- { name: 'patreon2', purchasable: false /* moulberry no longer offers */ },
- { name: 'fade', custom: `${rawCapeUrl}fade.gif`, purchasable: true },
- { name: 'lava', custom: `${rawCapeUrl}lava.gif`, purchasable: true },
- { name: 'mcworld', custom: `${rawCapeUrl}mcworld_compressed.gif`, purchasable: true },
- { name: 'negative', custom: `${rawCapeUrl}negative_compressed.gif`, purchasable: true },
- { name: 'space', custom: `${rawCapeUrl}space_compressed.gif`, purchasable: true },
- { name: 'void', custom: `${rawCapeUrl}void.gif`, purchasable: true },
- { name: 'tunnel', custom: `${rawCapeUrl}tunnel.gif`, purchasable: true },
- /* Staff capes */
- { name: 'contrib' },
- { name: 'mbstaff' },
- { name: 'ironmoon' },
- { name: 'gravy' },
- { name: 'nullzee' },
- /* partner capes */
- { name: 'thebakery' },
- { name: 'dsm' },
- { name: 'packshq' },
- { name: 'furf' },
- { name: 'skytils' },
- { name: 'sbp' },
- { name: 'subreddit_light' },
- { name: 'subreddit_dark' },
- { name: 'skyclient' },
- { name: 'sharex' },
- { name: 'sharex_white' },
- /* streamer capes */
- { name: 'alexxoffi' },
- { name: 'jakethybro' },
- { name: 'krusty' },
- { name: 'krusty_day' },
- { name: 'krusty_night' },
- { name: 'krusty_sunset' },
- { name: 'soldier' },
- { name: 'zera' },
- { name: 'secondpfirsisch' },
- { name: 'stormy_lh' }
- ].map((value, index) => ({ ...value, index })),
-
- roleMap: [
- { name: '*', id: '792453550768390194' },
- { name: 'Admin Perms', id: '746541309853958186' },
- { name: 'Sr. Moderator', id: '782803470205190164' },
- { name: 'Moderator', id: '737308259823910992' },
- { name: 'Helper', id: '737440116230062091' },
- { name: 'Trial Helper', id: '783537091946479636' },
- { name: 'Contributor', id: '694431057532944425' },
- { name: 'Giveaway Donor', id: '784212110263451649' },
- { name: 'Giveaway (200m)', id: '810267756426690601' },
- { name: 'Giveaway (100m)', id: '801444430522613802' },
- { name: 'Giveaway (50m)', id: '787497512981757982' },
- { name: 'Giveaway (25m)', id: '787497515771232267' },
- { name: 'Giveaway (10m)', id: '787497518241153025' },
- { name: 'Giveaway (5m)', id: '787497519768403989' },
- { name: 'Giveaway (1m)', id: '787497521084891166' },
- { name: 'Suggester', id: '811922322767609877' },
- { name: 'Partner', id: '767324547312779274' },
- { name: 'Level Locked', id: '784248899044769792' },
- { name: 'No Files', id: '786421005039173633' },
- { name: 'No Reactions', id: '786421270924361789' },
- { name: 'No Links', id: '786421269356740658' },
- { name: 'No Bots', id: '786804858765312030' },
- { name: 'No VC', id: '788850482554208267' },
- { name: 'No Giveaways', id: '808265422334984203' },
- { name: 'No Support', id: '790247359824396319' }
- ],
-
- roleWhitelist: {
- 'Partner': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Suggester': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper', 'Trial Helper', 'Contributor'],
- 'Level Locked': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Files': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Reactions': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Links': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Bots': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No VC': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'No Giveaways': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator', 'Helper'],
- 'No Support': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway Donor': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (200m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (100m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (50m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (25m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (10m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (5m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator'],
- 'Giveaway (1m)': ['*', 'Admin Perms', 'Sr. Moderator', 'Moderator']
- }
-} as const);
-
-export const ArgumentMatches = Object.freeze({
- ...AkairoArgumentMatches
-} as const);
-
-export const ArgumentTypes = Object.freeze({
- ...AkairoArgumentTypes,
- 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);
-
-export const BlockedReasons = Object.freeze({
- ...BuiltInReasons,
- 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);
-
-export const CommandHandlerEvents = Object.freeze({
- ...AkairoCommandHandlerEvents
-} as const);
-
-export const moulberryBushRoleMap = deepLock([
- { name: '*', id: '792453550768390194' },
- { name: 'Admin Perms', id: '746541309853958186' },
- { name: 'Sr. Moderator', id: '782803470205190164' },
- { name: 'Moderator', id: '737308259823910992' },
- { name: 'Helper', id: '737440116230062091' },
- { name: 'Trial Helper', id: '783537091946479636' },
- { name: 'Contributor', id: '694431057532944425' },
- { name: 'Giveaway Donor', id: '784212110263451649' },
- { name: 'Giveaway (200m)', id: '810267756426690601' },
- { name: 'Giveaway (100m)', id: '801444430522613802' },
- { name: 'Giveaway (50m)', id: '787497512981757982' },
- { name: 'Giveaway (25m)', id: '787497515771232267' },
- { name: 'Giveaway (10m)', id: '787497518241153025' },
- { name: 'Giveaway (5m)', id: '787497519768403989' },
- { name: 'Giveaway (1m)', id: '787497521084891166' },
- { name: 'Suggester', id: '811922322767609877' },
- { name: 'Partner', id: '767324547312779274' },
- { name: 'Level Locked', id: '784248899044769792' },
- { name: 'No Files', id: '786421005039173633' },
- { name: 'No Reactions', id: '786421270924361789' },
- { name: 'No Links', id: '786421269356740658' },
- { name: 'No Bots', id: '786804858765312030' },
- { name: 'No VC', id: '788850482554208267' },
- { name: 'No Giveaways', id: '808265422334984203' },
- { name: 'No Support', id: '790247359824396319' }
-] as const);
-
-export type PronounCode = keyof typeof pronounMapping;
-export type Pronoun = typeof pronounMapping[PronounCode];
diff --git a/src/lib/utils/BushLogger.ts b/src/lib/utils/BushLogger.ts
deleted file mode 100644
index 4acda69..0000000
--- a/src/lib/utils/BushLogger.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-import chalk from 'chalk';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { bold, Client, EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js';
-import { stripVTControlCharacters as stripColor } from 'node:util';
-import repl, { REPLServer, REPL_MODE_STRICT } from 'repl';
-import { WriteStream } from 'tty';
-import { type SendMessageType } from '../extensions/discord-akairo/BushClient.js';
-import { colors } from './BushConstants.js';
-import { inspect } from './BushUtils.js';
-
-let REPL: REPLServer;
-let replGone = false;
-
-export function init() {
- const kFormatForStdout = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStdout)')!;
- const kFormatForStderr = Object.getOwnPropertySymbols(console).find((sym) => sym.toString() === 'Symbol(kFormatForStderr)')!;
-
- REPL = repl.start({
- useColors: true,
- terminal: true,
- useGlobal: true,
- replMode: REPL_MODE_STRICT,
- breakEvalOnSigint: true,
- ignoreUndefined: true
- });
-
- const apply = (stream: WriteStream, symbol: symbol): ProxyHandler<typeof console['log']>['apply'] =>
- function apply(target, thisArg, args) {
- if (stream.isTTY) {
- stream.moveCursor(0, -1);
- stream.write('\n');
- stream.clearLine(0);
- }
-
- const ret = target(...args);
-
- if (stream.isTTY) {
- const formatted = (console as any)[symbol](args) as string;
-
- stream.moveCursor(0, formatted.split('\n').length);
- if (!replGone) {
- REPL.displayPrompt(true);
- }
- }
-
- return ret;
- };
-
- global.console.log = new Proxy(console.log, {
- apply: apply(process.stdout, kFormatForStdout)
- });
-
- global.console.warn = new Proxy(console.warn, {
- apply: apply(process.stderr, kFormatForStderr)
- });
-
- REPL.on('exit', () => {
- replGone = true;
- process.exit(0);
- });
-}
-
-/**
- * Parses the content surrounding by `<<>>` and emphasizes it with the given color or by making it bold.
- * @param content The content to parse.
- * @param color The color to emphasize the content with.
- * @param discordFormat Whether or not to format the content for discord.
- * @returns The formatted content.
- */
-function parseFormatting(
- content: any,
- color: 'blueBright' | 'blackBright' | 'redBright' | 'yellowBright' | 'greenBright' | '',
- discordFormat = false
-): string | typeof content {
- if (typeof content !== 'string') return content;
- return content
- .split(/<<|>>/)
- .map((value, index) => {
- if (discordFormat) {
- return index % 2 === 0 ? escapeMarkdown(value) : bold(escapeMarkdown(value));
- } else {
- return index % 2 === 0 || !color ? value : chalk[color](value);
- }
- })
- .join('');
-}
-
-/**
- * Inspects the content and returns a string.
- * @param content The content to inspect.
- * @param depth The depth the content will inspected. Defaults to `2`.
- * @param colors Whether or not to use colors in the output. Defaults to `true`.
- * @returns The inspected content.
- */
-function inspectContent(content: any, depth = 2, colors = true): string {
- if (typeof content !== 'string') {
- return inspect(content, { depth, colors });
- }
- return content;
-}
-
-/**
- * Generates a formatted timestamp for logging.
- * @returns The formatted timestamp.
- */
-function getTimeStamp(): string {
- const now = new Date();
- const minute = pad(now.getMinutes());
- const hour = pad(now.getHours());
- const date = `${pad(now.getMonth() + 1)}/${pad(now.getDate())}`;
- return `${date} ${hour}:${minute}`;
-}
-
-/**
- * Pad a two-digit number.
- */
-function pad(num: number) {
- return num.toString().padStart(2, '0');
-}
-
-/**
- * Custom logging utility for the bot.
- */
-export class BushLogger {
- /**
- * @param client The client.
- */
- public constructor(public client: Client) {}
-
- /**
- * 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.
- */
- public get log() {
- return this.info;
- }
-
- /**
- * Sends a message to the log channel.
- * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
- * @returns The message sent.
- */
- public async channelLog(message: SendMessageType): Promise<Message | null> {
- const channel = await this.client.utils.getConfigChannel('log');
- if (channel === null) return null;
- return await channel.send(message).catch(() => null);
- }
-
- /**
- * Sends a message to the error channel.
- * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
- * @returns The message sent.
- */
- public async channelError(message: SendMessageType): Promise<Message | null> {
- const channel = await this.client.utils.getConfigChannel('error');
- if (!channel) {
- void this.error(
- 'BushLogger',
- `Could not find error channel, was originally going to send: \n${inspect(message, {
- colors: true
- })}\n${new Error().stack?.substring(8)}`,
- false
- );
- return null;
- }
- return await channel.send(message);
- }
-
- /**
- * 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`.
- */
- public debug(content: any, depth = 0): void {
- if (!this.client.config.isDevelopment) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')} ${newContent}`);
- }
-
- /**
- * Logs raw debug information. Only works in dev is enabled in the config.
- * @param content The content to log.
- */
- public debugRaw(...content: any): void {
- if (!this.client.config.isDevelopment) return;
- console.log(`${chalk.bgMagenta(getTimeStamp())} ${chalk.magenta('[Debug]')}`, ...content);
- }
-
- /**
- * 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`.
- */
- public async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgGrey(getTimeStamp())} ${chalk.grey(`[${header}]`)} ${parseFormatting(newContent, 'blackBright')}`);
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.gray)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] });
- }
-
- /**
- * Logs very verbose information. Highlight information by surrounding it in `<<>>`.
- * @param header The header printed before the content, displayed in purple.
- * @param content The content to log, highlights displayed in bright black.
- * @param depth The depth the content will inspected. Defaults to `0`.
- */
- public async superVerbose(header: string, content: any, depth = 0): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- const newContent = inspectContent(content, depth, true);
- console.log(
- `${chalk.bgHex('#949494')(getTimeStamp())} ${chalk.hex('#949494')(`[${header}]`)} ${chalk.hex('#b3b3b3')(newContent)}`
- );
- }
-
- /**
- * Logs raw very verbose information.
- * @param header The header printed before the content, displayed in purple.
- * @param content The content to log.
- */
- public async superVerboseRaw(header: string, ...content: any[]): Promise<void> {
- if (!this.client.config.logging.verbose) return;
- console.log(`${chalk.bgHex('#a3a3a3')(getTimeStamp())} ${chalk.hex('#a3a3a3')(`[${header}]`)}`, ...content);
- }
-
- /**
- * 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`.
- */
- public async info(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- if (!this.client.config.logging.info) return;
- const newContent = inspectContent(content, depth, true);
- console.log(`${chalk.bgCyan(getTimeStamp())} ${chalk.cyan(`[${header}]`)} ${parseFormatting(newContent, 'blueBright')}`);
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.info)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] });
- }
-
- /**
- * 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`.
- */
- public async warn(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.warn(
- `${chalk.bgYellow(getTimeStamp())} ${chalk.yellow(`[${header}]`)} ${parseFormatting(newContent, 'yellowBright')}`
- );
-
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.warn)
- .setTimestamp();
- await this.channelError({ embeds: [embed] });
- }
-
- /**
- * 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`.
- */
- public async error(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.warn(
- `${chalk.bgRedBright(getTimeStamp())} ${chalk.redBright(`[${header}]`)} ${parseFormatting(newContent, 'redBright')}`
- );
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.error)
- .setTimestamp();
- await this.channelError({ embeds: [embed] });
- return;
- }
-
- /**
- * 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`.
- */
- public async success(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- const newContent = inspectContent(content, depth, true);
- console.log(
- `${chalk.bgGreen(getTimeStamp())} ${chalk.greenBright(`[${header}]`)} ${parseFormatting(newContent, 'greenBright')}`
- );
- if (!sendChannel) return;
- const embed = new EmbedBuilder()
- .setDescription(`**[${header}]** ${parseFormatting(stripColor(newContent), '', true)}`)
- .setColor(colors.success)
- .setTimestamp();
- await this.channelLog({ embeds: [embed] }).catch(() => {});
- }
-}
diff --git a/src/lib/utils/BushUtils.ts b/src/lib/utils/BushUtils.ts
deleted file mode 100644
index 75aded3..0000000
--- a/src/lib/utils/BushUtils.ts
+++ /dev/null
@@ -1,612 +0,0 @@
-import {
- Arg,
- BushClient,
- CommandMessage,
- SlashEditMessageType,
- SlashSendMessageType,
- timeUnits,
- type BaseBushArgumentType,
- type BushInspectOptions,
- type SlashMessage
-} from '#lib';
-import { humanizeDuration as humanizeDurationMod } from '@notenoughupdates/humanize-duration';
-import assert from 'assert/strict';
-import cp from 'child_process';
-import deepLock from 'deep-lock';
-import { Util as AkairoUtil } from 'discord-akairo';
-import {
- Constants as DiscordConstants,
- EmbedBuilder,
- Message,
- OAuth2Scopes,
- PermissionFlagsBits,
- PermissionsBitField,
- type APIEmbed,
- type APIMessage,
- type CommandInteraction,
- type InteractionReplyOptions,
- type PermissionsString
-} from 'discord.js';
-import got from 'got';
-import { DeepWritable } from 'ts-essentials';
-import { inspect as inspectUtil, promisify } from 'util';
-import * as Format from '../common/util/Format.js';
-
-export type StripPrivate<T> = { [K in keyof T]: T[K] extends Record<string, any> ? StripPrivate<T[K]> : T[K] };
-export type ValueOf<T> = T[keyof T];
-
-/**
- * Capitalizes the first letter of the given text
- * @param text The text to capitalize
- * @returns The capitalized text
- */
-export function capitalize(text: string): string {
- return text.charAt(0).toUpperCase() + text.slice(1);
-}
-
-export const exec = promisify(cp.exec);
-
-/**
- * Runs a shell command and gives the output
- * @param command The shell command to run
- * @returns The stdout and stderr of the shell command
- */
-export async function shell(command: string): Promise<{ stdout: string; stderr: string }> {
- return await exec(command);
-}
-
-/**
- * Appends the correct ordinal to the given number
- * @param n The number to append an ordinal to
- * @returns The number with the ordinal
- */
-export function ordinal(n: number): string {
- const s = ['th', 'st', 'nd', 'rd'],
- v = n % 100;
- return n + (s[(v - 20) % 10] || s[v] || s[0]);
-}
-
-/**
- * Chunks an array to the specified size
- * @param arr The array to chunk
- * @param perChunk The amount of items per chunk
- * @returns The chunked array
- */
-export function chunk<T>(arr: T[], perChunk: number): T[][] {
- return arr.reduce((all, one, i) => {
- const ch: number = Math.floor(i / perChunk);
- (all as any[])[ch] = [].concat(all[ch] || [], one as any);
- return all;
- }, []);
-}
-
-/**
- * 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.
- */
-export async function 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, '');
-}
-
-export interface UuidRes {
- uuid: string;
- username: string;
- username_history?: { username: string }[] | null;
- textures: {
- custom: boolean;
- slim: boolean;
- skin: {
- url: string;
- data: string;
- };
- raw: {
- value: string;
- signature: string;
- };
- };
- created_at: string;
-}
-
-/**
- * Generate defaults for {@link inspect}.
- * @param options The options to create defaults with.
- * @returns The default options combined with the specified options.
- */
-function getDefaultInspectOptions(options?: BushInspectOptions): BushInspectOptions {
- return {
- showHidden: options?.showHidden ?? false,
- depth: options?.depth ?? 2,
- colors: options?.colors ?? false,
- customInspect: options?.customInspect ?? true,
- showProxy: options?.showProxy ?? false,
- maxArrayLength: options?.maxArrayLength ?? Infinity,
- maxStringLength: options?.maxStringLength ?? Infinity,
- breakLength: options?.breakLength ?? 80,
- compact: options?.compact ?? 3,
- sorted: options?.sorted ?? false,
- getters: options?.getters ?? true,
- numericSeparator: options?.numericSeparator ?? true
- };
-}
-
-/**
- * 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.
- */
-export function inspect(object: any, options?: BushInspectOptions): string {
- const optionsWithDefaults = getDefaultInspectOptions(options);
-
- if (!optionsWithDefaults.inspectStrings && typeof object === 'string') return object;
-
- return inspectUtil(object, optionsWithDefaults);
-}
-
-/**
- * Responds to a slash command interaction.
- * @param interaction The interaction to respond to.
- * @param responseOptions The options for the response.
- * @returns The message sent.
- */
-export async function slashRespond(
- interaction: CommandInteraction,
- responseOptions: SlashSendMessageType | SlashEditMessageType
-): Promise<Message | APIMessage | undefined> {
- const newResponseOptions = typeof responseOptions === 'string' ? { content: responseOptions } : responseOptions;
- if (interaction.replied || interaction.deferred) {
- delete (newResponseOptions as InteractionReplyOptions).ephemeral; // Cannot change a preexisting message to be ephemeral
- return (await interaction.editReply(newResponseOptions)) as Message | APIMessage;
- } else {
- await interaction.reply(newResponseOptions);
- return await interaction.fetchReply().catch(() => undefined);
- }
-}
-
-/**
- * Takes an array and combines the elements using the supplied conjunction.
- * @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`.
- *
- * @example
- * const permissions = oxford(['Administrator', 'SendMessages', 'ManageMessages'], 'and', 'none');
- * console.log(permissions); // Administrator, SendMessages and ManageMessages
- */
-export function oxford(array: string[], conjunction: string, ifEmpty?: string): string | undefined {
- const l = array.length;
- if (!l) return ifEmpty;
- if (l < 2) return array[0];
- if (l < 3) return array.join(` ${conjunction} `);
- array = array.slice();
- array[l - 1] = `${conjunction} ${array[l - 1]}`;
- return array.join(', ');
-}
-
-/**
- * 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.
- */
-export function addOrRemoveFromArray<T>(action: 'add' | 'remove', array: T[], value: T): T[] {
- const set = new Set(array);
- action === 'add' ? set.add(value) : set.delete(value);
- return [...set];
-}
-
-/**
- * Remove an item from an array. All duplicates will be removed.
- * @param array The array to remove an element from.
- * @param value The element to remove from the array.
- */
-export function removeFromArray<T>(array: T[], value: T): T[] {
- return addOrRemoveFromArray('remove', array, value);
-}
-
-/**
- * Add an item from an array. All duplicates will be removed.
- * @param array The array to add an element to.
- * @param value The element to add to the array.
- */
-export function addToArray<T>(array: T[], value: T): T[] {
- return addOrRemoveFromArray('add', array, value);
-}
-
-/**
- * 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`.
- */
-export function surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] {
- return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`);
-}
-
-/**
- * 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}.
- */
-export function parseDuration(content: string, remove = true): ParsedDuration {
- if (!content) return { duration: 0, content: null };
-
- // eslint-disable-next-line prefer-const
- let duration: number | null = null;
- // Try to reduce false positives by requiring a space before the duration, this makes sure it still matches if it is
- // in the beginning of the argument
- let contentWithoutTime = ` ${content}`;
-
- for (const unit in timeUnits) {
- const regex = timeUnits[unit as keyof typeof timeUnits].match;
- const match = regex.exec(contentWithoutTime);
- const value = Number(match?.groups?.[unit]);
- if (!isNaN(value)) duration! += value * timeUnits[unit as keyof typeof timeUnits].value;
-
- if (remove) contentWithoutTime = contentWithoutTime.replace(regex, '');
- }
- // remove the space added earlier
- if (contentWithoutTime.startsWith(' ')) contentWithoutTime.replace(' ', '');
- return { duration, content: contentWithoutTime };
-}
-
-export interface ParsedDuration {
- duration: number | null;
- content: string | null;
-}
-
-/**
- * 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.
- * @param round Whether or not to round the smallest unit displayed.
- * @returns A humanized string of the duration.
- */
-export function humanizeDuration(duration: number, largest?: number, round = true): string {
- if (largest) return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, largest, round })!;
- else return humanizeDurationMod(duration, { language: 'en', maxDecimalPoints: 2, round })!;
-}
-
-/**
- * Creates a formatted relative timestamp from a duration in milliseconds.
- * @param duration The duration in milliseconds.
- * @returns The formatted relative timestamp.
- */
-export function timestampDuration(duration: number): string {
- 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 ex. `16:20`
- * - **T**: Long Time ex. `16:20:30 `
- * - **d**: Short Date ex. `20/04/2021`
- * - **D**: Long Date ex. `20 April 2021`
- * - **f**: Short Date/Time ex. `20 April 2021 16:20`
- * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20`
- * - **R**: Relative Time ex. `2 months ago`
- */
-export function timestamp<D extends Date | undefined | null>(
- date: D,
- style: TimestampStyle = 'f'
-): D extends Date ? string : undefined {
- if (!date) return date as unknown as D extends Date ? string : undefined;
- return `<t:${Math.round(date.getTime() / 1_000)}:${style}>` as unknown as D extends Date ? string : undefined;
-}
-
-export type TimestampStyle = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R';
-
-/**
- * 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.
- * @param round Whether or not to round the smallest unit displayed.
- * @returns A humanized string of the delta.
- */
-export function dateDelta(date: Date, largest = 3, round = true): string {
- return humanizeDuration(new Date().getTime() - date.getTime(), largest, round);
-}
-
-/**
- * Combines {@link timestamp} and {@link dateDelta}
- * @param date The date to be compared with the current time.
- * @param style The style of the timestamp.
- * @returns The formatted timestamp.
- *
- * @see
- * **Styles:**
- * - **t**: Short Time ex. `16:20`
- * - **T**: Long Time ex. `16:20:30 `
- * - **d**: Short Date ex. `20/04/2021`
- * - **D**: Long Date ex. `20 April 2021`
- * - **f**: Short Date/Time ex. `20 April 2021 16:20`
- * - **F**: Long Date/Time ex. `Tuesday, 20 April 2021 16:20`
- * - **R**: Relative Time ex. `2 months ago`
- */
-export function timestampAndDelta(date: Date, style: TimestampStyle = 'D'): string {
- return `${timestamp(date, style)} (${dateDelta(date)} ago)`;
-}
-
-/**
- * Convert a hex code to an rbg value.
- * @param hex The hex code to convert.
- * @returns The rbg value.
- */
-export function hexToRgb(hex: string): string {
- const arrBuff = new ArrayBuffer(4);
- const vw = new DataView(arrBuff);
- vw.setUint32(0, parseInt(hex, 16), false);
- const arrByte = new Uint8Array(arrBuff);
-
- return `${arrByte[1]}, ${arrByte[2]}, ${arrByte[3]}`;
-}
-
-/**
- * Wait an amount in milliseconds.
- * @returns A promise that resolves after the specified amount of milliseconds
- */
-export const sleep = promisify(setTimeout);
-
-/**
- * List the methods of an object.
- * @param obj The object to get the methods of.
- * @returns A string with each method on a new line.
- */
-export function getMethods(obj: Record<string, any>): string {
- // modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class/31055217#31055217
- let props: string[] = [];
- let obj_: Record<string, any> = new Object(obj);
-
- do {
- 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
- 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
- );
-
- const reg = /\(([\s\S]*?)\)/;
- props = props.concat(
- l.map(
- (p) =>
- `${obj_[p] && obj_[p][Symbol.toStringTag] === 'AsyncFunction' ? 'async ' : ''}function ${p}(${
- reg.exec(obj_[p].toString())?.[1]
- ? reg
- .exec(obj_[p].toString())?.[1]
- .split(', ')
- .map((arg) => arg.split('=')[0].trim())
- .join(', ')
- : ''
- });`
- )
- );
- } while (
- (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain
- Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...)
- );
-
- return props.join('\n');
-}
-
-/**
- * List the symbols of an object.
- * @param obj The object to get the symbols of.
- * @returns An array of the symbols of the object.
- */
-export function getSymbols(obj: Record<string, any>): symbol[] {
- let symbols: symbol[] = [];
- let obj_: Record<string, any> = new Object(obj);
-
- do {
- const l = Object.getOwnPropertySymbols(obj_).sort();
-
- symbols = [...symbols, ...l];
- } while (
- (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain
- Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...)
- );
-
- return symbols;
-}
-
-/**
- * Checks if a user has a certain guild permission (doesn't check channel permissions).
- * @param message The message to check the user from.
- * @param permissions The permissions to check for.
- * @returns The missing permissions or null if none are missing.
- */
-export function userGuildPermCheck(
- message: CommandMessage | SlashMessage,
- permissions: typeof PermissionFlagsBits[keyof typeof PermissionFlagsBits][]
-): PermissionsString[] | null {
- if (!message.inGuild()) return null;
- const missing = message.member?.permissions.missing(permissions) ?? [];
-
- return missing.length ? missing : null;
-}
-
-/**
- * Check if the client has certain permissions in the guild (doesn't check channel permissions).
- * @param message The message to check the client user from.
- * @param permissions The permissions to check for.
- * @returns The missing permissions or null if none are missing.
- */
-export function clientGuildPermCheck(message: CommandMessage | SlashMessage, permissions: bigint[]): PermissionsString[] | null {
- const missing = message.guild?.members.me?.permissions.missing(permissions) ?? [];
-
- return missing.length ? missing : null;
-}
-
-/**
- * Check if the client has permission to send messages in the channel as well as check if they have other permissions
- * in the guild (or the channel if `checkChannel` is `true`).
- * @param message The message to check the client user from.
- * @param permissions The permissions to check for.
- * @param checkChannel Whether to check the channel permissions instead of the guild permissions.
- * @returns The missing permissions or null if none are missing.
- */
-export function clientSendAndPermCheck(
- message: CommandMessage | SlashMessage,
- permissions: bigint[] = [],
- checkChannel = false
-): PermissionsString[] | null {
- if (!message.inGuild() || !message.channel) return null;
-
- const missing: PermissionsString[] = [];
- const sendPerm = message.channel.isThread() ? 'SendMessages' : 'SendMessagesInThreads';
-
- // todo: remove once forum channels are fixed
- if (message.channel.parent === null && message.channel.isThread()) return null;
-
- if (!message.guild.members.me!.permissionsIn(message.channel!.id).has(sendPerm)) missing.push(sendPerm);
-
- missing.push(
- ...(checkChannel
- ? message.guild!.members.me!.permissionsIn(message.channel!.id!).missing(permissions)
- : clientGuildPermCheck(message, permissions) ?? [])
- );
-
- return missing.length ? missing : null;
-}
-
-export { deepLock as deepFreeze };
-export { Arg as arg };
-export { Format as format };
-export { DiscordConstants as discordConstants };
-export { AkairoUtil as akairo };
-
-/**
- * The link to invite the bot with all permissions.
- */
-export function invite(client: BushClient) {
- return client.generateInvite({
- permissions:
- PermissionsBitField.All -
- PermissionFlagsBits.UseEmbeddedActivities -
- PermissionFlagsBits.ViewGuildInsights -
- PermissionFlagsBits.Stream,
- scopes: [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands]
- });
-}
-
-/**
- * Asset multiple statements at a time.
- * @param args
- */
-export function assertAll(...args: any[]): void {
- for (let i = 0; i < args.length; i++) {
- assert(args[i], `assertAll index ${i} failed`);
- }
-}
-
-/**
- * Casts a string to a duration and reason for slash commands.
- * @param arg The argument received.
- * @param message The message that triggered the command.
- * @returns The casted argument.
- */
-export async function castDurationContent(
- arg: string | ParsedDuration | null,
- message: CommandMessage | SlashMessage
-): Promise<ParsedDurationRes> {
- const res = typeof arg === 'string' ? await Arg.cast('contentWithDuration', message, arg) : arg;
-
- return { duration: res?.duration ?? 0, content: res?.content ?? '' };
-}
-
-export interface ParsedDurationRes {
- duration: number;
- content: string;
-}
-
-/**
- * Casts a string to a the specified argument type.
- * @param type The type of the argument to cast to.
- * @param arg The argument received.
- * @param message The message that triggered the command.
- * @returns The casted argument.
- */
-export async function cast<T extends keyof BaseBushArgumentType>(
- type: T,
- arg: BaseBushArgumentType[T] | string,
- message: CommandMessage | SlashMessage
-) {
- return typeof arg === 'string' ? await Arg.cast(type, message, arg) : arg;
-}
-
-/**
- * Overflows the description of an embed into multiple embeds.
- * @param embed The options to be applied to the (first) embed.
- * @param lines Each line of the description as an element in an array.
- */
-export function overflowEmbed(embed: Omit<APIEmbed, 'description'>, lines: string[], maxLength = 4096): EmbedBuilder[] {
- const embeds: EmbedBuilder[] = [];
-
- const makeEmbed = () => {
- embeds.push(new EmbedBuilder().setColor(embed.color ?? null));
- return embeds.at(-1)!;
- };
-
- for (const line of lines) {
- let current = embeds.length ? embeds.at(-1)! : makeEmbed();
- let joined = current.data.description ? `${current.data.description}\n${line}` : line;
- if (joined.length > maxLength) {
- current = makeEmbed();
- joined = line;
- }
-
- current.setDescription(joined);
- }
-
- if (!embeds.length) makeEmbed();
-
- if (embed.author) embeds.at(0)?.setAuthor(embed.author);
- if (embed.title) embeds.at(0)?.setTitle(embed.title);
- if (embed.url) embeds.at(0)?.setURL(embed.url);
- if (embed.fields) embeds.at(-1)?.setFields(embed.fields);
- if (embed.thumbnail) embeds.at(-1)?.setThumbnail(embed.thumbnail.url);
- if (embed.footer) embeds.at(-1)?.setFooter(embed.footer);
- if (embed.image) embeds.at(-1)?.setImage(embed.image.url);
- if (embed.timestamp) embeds.at(-1)?.setTimestamp(new Date(embed.timestamp));
-
- return embeds;
-}
-
-/**
- * Formats an error into a string.
- * @param error The error to format.
- * @param colors Whether to use colors in the output.
- * @returns The formatted error.
- */
-export function formatError(error: Error | any, colors = false): string {
- if (!error) return error;
- if (typeof error !== 'object') return String.prototype.toString.call(error);
- if (
- getSymbols(error)
- .map((s) => s.toString())
- .includes('Symbol(nodejs.util.inspect.custom)')
- )
- return inspect(error, { colors });
-
- return error.stack;
-}
-
-export function deepWriteable<T>(obj: T): DeepWritable<T> {
- return obj as DeepWritable<T>;
-}
diff --git a/src/lib/utils/CanvasProgressBar.ts b/src/lib/utils/CanvasProgressBar.ts
deleted file mode 100644
index fb4f778..0000000
--- a/src/lib/utils/CanvasProgressBar.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { CanvasRenderingContext2D } from 'canvas';
-
-/**
- * I just copy pasted this code from stackoverflow don't yell at me if there is issues for it
- * @author @TymanWasTaken
- */
-export class CanvasProgressBar {
- private readonly x: number;
- private readonly y: number;
- private readonly w: number;
- private readonly h: number;
- private readonly color: string;
- private percentage: number;
- private p?: number;
- private ctx: CanvasRenderingContext2D;
-
- public constructor(
- ctx: CanvasRenderingContext2D,
- dimension: { x: number; y: number; width: number; height: number },
- color: string,
- percentage: number
- ) {
- ({ x: this.x, y: this.y, width: this.w, height: this.h } = dimension);
- this.color = color;
- this.percentage = percentage;
- this.p = undefined;
- this.ctx = ctx;
- }
-
- public draw(): void {
- // -----------------
- this.p = this.percentage * this.w;
- if (this.p <= this.h) {
- this.ctx.beginPath();
- this.ctx.arc(
- this.h / 2 + this.x,
- this.h / 2 + this.y,
- this.h / 2,
- Math.PI - Math.acos((this.h - this.p) / this.h),
- Math.PI + Math.acos((this.h - this.p) / this.h)
- );
- this.ctx.save();
- this.ctx.scale(-1, 1);
- this.ctx.arc(
- this.h / 2 - this.p - this.x,
- this.h / 2 + this.y,
- this.h / 2,
- Math.PI - Math.acos((this.h - this.p) / this.h),
- Math.PI + Math.acos((this.h - this.p) / this.h)
- );
- this.ctx.restore();
- this.ctx.closePath();
- } else {
- this.ctx.beginPath();
- this.ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, (3 / 2) * Math.PI);
- this.ctx.lineTo(this.p - this.h + this.x, 0 + this.y);
- this.ctx.arc(this.p - this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, (3 / 2) * Math.PI, Math.PI / 2);
- this.ctx.lineTo(this.h / 2 + this.x, this.h + this.y);
- this.ctx.closePath();
- }
- this.ctx.fillStyle = this.color;
- this.ctx.fill();
- }
-
- // public showWholeProgressBar(){
- // this.ctx.beginPath();
- // this.ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, 3 / 2 * Math.PI);
- // this.ctx.lineTo(this.w - this.h + this.x, 0 + this.y);
- // this.ctx.arc(this.w - this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, 3 / 2 *Math.PI, Math.PI / 2);
- // this.ctx.lineTo(this.h / 2 + this.x, this.h + this.y);
- // this.ctx.strokeStyle = '#000000';
- // this.ctx.stroke();
- // this.ctx.closePath();
- // }
-
- public get PPercentage(): number {
- return this.percentage * 100;
- }
-
- public set PPercentage(x: number) {
- this.percentage = x / 100;
- }
-}