aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/common/AutoMod.ts30
-rw-r--r--src/lib/common/ButtonPaginator.ts2
-rw-r--r--src/lib/common/ConfirmationPrompt.ts2
-rw-r--r--src/lib/common/DeleteButton.ts2
-rw-r--r--src/lib/common/HighlightManager.ts8
-rw-r--r--src/lib/common/util/Arg.ts2
-rw-r--r--src/lib/common/util/Moderation.ts53
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts89
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts8
-rw-r--r--src/lib/extensions/discord-akairo/BushCommandHandler.ts7
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitor.ts8
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuild.ts48
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuildMember.ts77
-rw-r--r--src/lib/extensions/discord.js/ExtendedMessage.ts2
-rw-r--r--src/lib/extensions/discord.js/ExtendedUser.ts4
-rw-r--r--src/lib/extensions/global.ts8
-rw-r--r--src/lib/models/instance/Guild.ts4
-rw-r--r--src/lib/utils/BushClientUtils.ts480
-rw-r--r--src/lib/utils/BushLogger.ts77
-rw-r--r--src/lib/utils/BushUtils.ts461
20 files changed, 743 insertions, 629 deletions
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 7f19e63..0910352 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -1,4 +1,4 @@
-import { banResponse, codeblock, colors, emojis, format, formatError, getShared, Moderation, resolveNonCachedUser } from '#lib';
+import { banResponse, colors, emojis, format, formatError, Moderation } from '#lib';
import assert from 'assert';
import chalk from 'chalk';
import {
@@ -31,7 +31,7 @@ export class AutoMod {
*/
private message: Message
) {
- if (message.author.id === client.user?.id) return;
+ if (message.author.id === message.client.user?.id) return;
void this.handle();
}
@@ -56,9 +56,9 @@ export class AutoMod {
traditional: {
if (this.isImmune) break traditional;
- const badLinksArray = getShared('badLinks');
- const badLinksSecretArray = getShared('badLinksSecret');
- const badWordsRaw = getShared('badWords');
+ const badLinksArray = this.message.client.utils.getShared('badLinks');
+ const badLinksSecretArray = this.message.client.utils.getShared('badLinksSecret');
+ const badWordsRaw = this.message.client.utils.getShared('badWords');
const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? [];
const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])];
@@ -167,7 +167,9 @@ export class AutoMod {
.setDescription(
`**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})`
)
- .addFields([{ name: 'Message Content', value: `${await codeblock(this.message.content, 1024)}` }])
+ .addFields([
+ { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` }
+ ])
.setColor(color)
.setTimestamp()
],
@@ -186,12 +188,12 @@ export class AutoMod {
private async checkPerspectiveApi() {
return;
- if (!client.config.isDevelopment) return;
+ if (!this.message.client.config.isDevelopment) return;
if (!this.message.content) return;
- client.perspective.comments.analyze(
+ this.message.client.perspective.comments.analyze(
{
- key: client.config.credentials.perspectiveApiKey,
+ key: this.message.client.config.credentials.perspectiveApiKey,
resource: {
comment: {
text: this.message.content
@@ -301,7 +303,7 @@ export class AutoMod {
{
title: 'AutoMod Error',
description: `Unable to delete triggered message.`,
- fields: [{ name: 'Error', value: await codeblock(`${formatError(e)}`, 1024, 'js', true) }],
+ fields: [{ name: 'Error', value: await this.message.client.utils.codeblock(`${formatError(e)}`, 1024, 'js', true) }],
color: colors.error
}
]
@@ -316,7 +318,7 @@ export class AutoMod {
* @param offences The other offences that were also matched in the message
*/
private async log(highestOffence: BadWordDetails, color: number, offences: BadWordDetails[]) {
- void client.console.info(
+ void this.message.client.console.info(
'autoMod',
`Severity <<${highestOffence.severity}>> action performed on <<${this.message.author.tag}>> (<<${
this.message.author.id
@@ -332,7 +334,9 @@ export class AutoMod {
this.message.channel.id
}> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${offences.map((o) => `\`${o.match}\``).join(', ')}`
)
- .addFields([{ name: 'Message Content', value: `${await codeblock(this.message.content, 1024)}` }])
+ .addFields([
+ { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` }
+ ])
.setColor(color)
.setTimestamp()
.setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() })
@@ -386,7 +390,7 @@ export class AutoMod {
evidence: (interaction.message as Message).url ?? undefined
});
- const victimUserFormatted = (await resolveNonCachedUser(userId))?.tag ?? userId;
+ const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId;
if (result === banResponse.SUCCESS)
return interaction.reply({
content: `${emojis.success} Successfully banned **${victimUserFormatted}**.`,
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index 9560247..708b374 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -97,7 +97,7 @@ export class ButtonPaginator {
* @param interaction The interaction received
*/
protected async collect(interaction: MessageComponentInteraction) {
- if (interaction.user.id !== this.message.author.id && !client.config.owners.includes(interaction.user.id))
+ if (interaction.user.id !== this.message.author.id && !this.message.client.config.owners.includes(interaction.user.id))
return await interaction?.deferUpdate().catch(() => null);
switch (interaction.customId) {
diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts
index 4593d24..38d078a 100644
--- a/src/lib/common/ConfirmationPrompt.ts
+++ b/src/lib/common/ConfirmationPrompt.ts
@@ -43,7 +43,7 @@ export class ConfirmationPrompt {
collector.on('collect', async (interaction: MessageComponentInteraction) => {
await interaction.deferUpdate().catch(() => undefined);
- if (interaction.user.id == this.message.author.id || client.config.owners.includes(interaction.user.id)) {
+ if (interaction.user.id == this.message.author.id || this.message.client.config.owners.includes(interaction.user.id)) {
if (interaction.customId === 'confirmationPrompt_confirm') {
responded = true;
collector.stop();
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index b561d94..bc0da17 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -45,7 +45,7 @@ export class DeleteButton {
collector.on('collect', async (interaction: MessageComponentInteraction) => {
await interaction.deferUpdate().catch(() => undefined);
- if (interaction.user.id == this.message.author.id || client.config.owners.includes(interaction.user.id)) {
+ if (interaction.user.id == this.message.author.id || this.message.client.config.owners.includes(interaction.user.id)) {
if (msg.deletable && !CommandUtil.deletedMessages.has(msg.id)) await msg.delete();
}
});
diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts
index caaa6a5..cd89c89 100644
--- a/src/lib/common/HighlightManager.ts
+++ b/src/lib/common/HighlightManager.ts
@@ -232,10 +232,10 @@ export class HighlightManager {
const lastDM = this.lastedDMedUserCooldown.get(user);
if (!lastDM) break dmCooldown;
- const cooldown = client.ownerID.includes(user) ? OWNER_NOTIFY_COOLDOWN : NOTIFY_COOLDOWN;
+ const cooldown = message.client.ownerID.includes(user) ? OWNER_NOTIFY_COOLDOWN : NOTIFY_COOLDOWN;
if (new Date().getTime() - lastDM.getTime() < cooldown) {
- void client.console.verbose('Highlight', `User <<${user}>> has been dmed recently.`);
+ void message.client.console.verbose('Highlight', `User <<${user}>> has been dmed recently.`);
return false;
}
}
@@ -248,7 +248,7 @@ export class HighlightManager {
const talked = lastTalked.getTime();
if (now - talked < LAST_MESSAGE_COOLDOWN) {
- void client.console.verbose('Highlight', `User <<${user}>> has talked too recently.`);
+ void message.client.console.verbose('Highlight', `User <<${user}>> has talked too recently.`);
setTimeout(() => {
const newTalked = this.userLastTalkedCooldown.get(message.guildId)?.get(user)?.getTime();
@@ -268,7 +268,7 @@ export class HighlightManager {
.first(4)
.reverse();
- return client.users
+ return message.client.users
.send(user, {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
content: `In ${format.input(message.guild.name)} ${message.channel}, your highlight "${hl.word}" was matched:`,
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts
index a7795b1..325f821 100644
--- a/src/lib/common/util/Arg.ts
+++ b/src/lib/common/util/Arg.ts
@@ -18,7 +18,7 @@ export async function cast<T extends ATC>(type: T, message: CommandMessage | Sla
export async function cast<T extends KBAT>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<BAT[T]>;
export async function cast(type: AT | ATC, message: CommandMessage | SlashMessage, phrase: string): Promise<any>;
export async function cast(type: ATC | AT, message: CommandMessage | SlashMessage, phrase: string): Promise<any> {
- return Argument.cast(type as any, client.commandHandler.resolver, message as Message, phrase);
+ return Argument.cast(type as any, message.client.commandHandler.resolver, message as Message, phrase);
}
/**
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
index a08dfa4..cb6b4db 100644
--- a/src/lib/common/util/Moderation.ts
+++ b/src/lib/common/util/Moderation.ts
@@ -5,10 +5,8 @@ import {
emojis,
format,
Guild as GuildDB,
- handleError,
humanizeDuration,
ModLog,
- resolveNonCachedUser,
type ModLogType
} from '#lib';
import assert from 'assert';
@@ -16,6 +14,7 @@ import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
+ Client,
EmbedBuilder,
PermissionFlagsBits,
type Guild,
@@ -129,9 +128,9 @@ export async function createModLogEntry(
options: CreateModLogEntryOptions,
getCaseNumber = false
): Promise<{ log: ModLog | null; caseNum: number | null }> {
- const user = (await resolveNonCachedUser(options.user))!.id;
- const moderator = (await resolveNonCachedUser(options.moderator))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
+ const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id;
+ const moderator = (await options.client.utils.resolveNonCachedUser(options.moderator))!.id;
+ const guild = options.client.guilds.resolveId(options.guild)!;
return createModLogEntrySimple(
{
@@ -172,7 +171,7 @@ export async function createModLogEntrySimple(
hidden: options.hidden ?? false
});
const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => {
- await handleError('createModLogEntry', e);
+ await options.client.utils.handleError('createModLogEntry', e);
return null;
});
@@ -191,8 +190,8 @@ export async function createModLogEntrySimple(
*/
export async function createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> {
const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
- const user = (await resolveNonCachedUser(options.user))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
+ const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id;
+ const guild = options.client.guilds.resolveId(options.guild)!;
const type = findTypeEnum(options.type)!;
const entry = ActivePunishment.build(
@@ -201,7 +200,7 @@ export async function createPunishmentEntry(options: CreatePunishmentEntryOption
: { user, type, guild, expires, modlog: options.modlog }
);
return await entry.save().catch(async (e) => {
- await handleError('createPunishmentEntry', e);
+ await options.client.utils.handleError('createPunishmentEntry', e);
return null;
});
}
@@ -212,8 +211,8 @@ export async function createPunishmentEntry(options: CreatePunishmentEntryOption
* @returns Whether or not the entry was destroyed.
*/
export async function removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> {
- const user = await resolveNonCachedUser(options.user);
- const guild = client.guilds.resolveId(options.guild);
+ const user = await options.client.utils.resolveNonCachedUser(options.user);
+ const guild = options.client.guilds.resolveId(options.guild);
const type = findTypeEnum(options.type);
if (!user || !guild) return false;
@@ -226,13 +225,13 @@ export async function removePunishmentEntry(options: RemovePunishmentEntryOption
? { user: user.id, guild: guild, type, extraInfo: options.extraInfo }
: { user: user.id, guild: guild, type }
}).catch(async (e) => {
- await handleError('removePunishmentEntry', e);
+ await options.client.utils.handleError('removePunishmentEntry', e);
success = false;
});
if (entries) {
const promises = entries.map(async (entry) =>
entry.destroy().catch(async (e) => {
- await handleError('removePunishmentEntry', e);
+ await options.client.utils.handleError('removePunishmentEntry', e);
success = false;
})
);
@@ -298,9 +297,9 @@ export async function punishDM(options: PunishDMOptions): Promise<boolean> {
new ActionRowBuilder<ButtonBuilder>({
components: [
new ButtonBuilder({
- customId: `appeal;${punishmentToPresentTense(options.punishment)};${options.guild.id};${client.users.resolveId(
- options.user
- )};${options.modlog}`,
+ customId: `appeal;${punishmentToPresentTense(options.punishment)};${
+ options.guild.id
+ };${options.client.users.resolveId(options.user)};${options.modlog}`,
style: ButtonStyle.Primary,
label: 'Appeal'
}).toJSON()
@@ -308,7 +307,7 @@ export async function punishDM(options: PunishDMOptions): Promise<boolean> {
})
];
- const dmSuccess = await client.users
+ const dmSuccess = await options.client.users
.send(options.user, {
content,
embeds: dmEmbed ? [dmEmbed] : undefined,
@@ -318,7 +317,7 @@ export async function punishDM(options: PunishDMOptions): Promise<boolean> {
return !!dmSuccess;
}
-interface BaseCreateModLogEntryOptions {
+interface BaseCreateModLogEntryOptions extends BaseOptions {
/**
* The type of modlog entry.
*/
@@ -355,6 +354,11 @@ interface BaseCreateModLogEntryOptions {
*/
export interface CreateModLogEntryOptions extends BaseCreateModLogEntryOptions {
/**
+ * The client.
+ */
+ client: Client;
+
+ /**
* The user that a modlog entry is created for.
*/
user: GuildMemberResolvable;
@@ -393,7 +397,7 @@ export interface SimpleCreateModLogEntryOptions extends BaseCreateModLogEntryOpt
/**
* Options for creating a punishment entry.
*/
-export interface CreatePunishmentEntryOptions {
+export interface CreatePunishmentEntryOptions extends BaseOptions {
/**
* The type of punishment.
*/
@@ -428,7 +432,7 @@ export interface CreatePunishmentEntryOptions {
/**
* Options for removing a punishment entry.
*/
-export interface RemovePunishmentEntryOptions {
+export interface RemovePunishmentEntryOptions extends BaseOptions {
/**
* The type of punishment.
*/
@@ -453,7 +457,7 @@ export interface RemovePunishmentEntryOptions {
/**
* Options for sending a user a punishment dm.
*/
-export interface PunishDMOptions {
+export interface PunishDMOptions extends BaseOptions {
/**
* The modlog case id so the user can make an appeal.
*/
@@ -496,6 +500,13 @@ export interface PunishDMOptions {
channel?: Snowflake;
}
+interface BaseOptions {
+ /**
+ * The client.
+ */
+ client: Client;
+}
+
export type PunishmentTypeDM =
| 'warned'
| 'muted'
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index b382121..68b2599 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -63,7 +63,8 @@ import { Shared } from '../../models/shared/Shared.js';
import { Stat } from '../../models/shared/Stat.js';
import { AllowedMentions } from '../../utils/AllowedMentions.js';
import { BushCache } from '../../utils/BushCache.js';
-import BushLogger from '../../utils/BushLogger.js';
+import { BushClientUtils } from '../../utils/BushClientUtils.js';
+import { BushLogger } from '../../utils/BushLogger.js';
import { ExtendedGuild } from '../discord.js/ExtendedGuild.js';
import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js';
import { ExtendedMessage } from '../discord.js/ExtendedMessage.js';
@@ -76,14 +77,44 @@ const { Sequelize } = (await import('sequelize')).default;
declare module 'discord.js' {
export interface Client extends EventEmitter {
- /**
- * The ID of the owner(s).
- */
+ /** The ID of the owner(s). */
ownerID: Snowflake | Snowflake[];
- /**
- * The ID of the superUser(s).
- */
+ /** The ID of the superUser(s). */
superUserID: Snowflake | Snowflake[];
+ /** Whether or not the client is ready. */
+ customReady: boolean;
+ /** The configuration for the client. */
+ config: Config;
+ /** Stats for the client. */
+ stats: BushStats;
+ /** The handler for the bot's listeners. */
+ listenerHandler: BushListenerHandler;
+ /** The handler for the bot's command inhibitors. */
+ inhibitorHandler: BushInhibitorHandler;
+ /** The handler for the bot's commands. */
+ commandHandler: BushCommandHandler;
+ /** The handler for the bot's tasks. */
+ taskHandler: BushTaskHandler;
+ /** The handler for the bot's context menu commands. */
+ contextMenuCommandHandler: ContextMenuCommandHandler;
+ /** The database connection for this instance of the bot (production, beta, or development). */
+ instanceDB: SequelizeType;
+ /** The database connection that is shared between all instances of the bot. */
+ sharedDB: SequelizeType;
+ /** A custom logging system for the bot. */
+ logger: BushLogger;
+ /** Cached global and guild database data. */
+ cache: BushCache;
+ /** Sentry error reporting for the bot. */
+ sentry: typeof Sentry;
+ /** Manages most aspects of the highlight command */
+ highlightManager: HighlightManager;
+ /** The perspective api */
+ perspective: any;
+ /** Client utilities. */
+ utils: BushClientUtils;
+ /** A custom logging system for the bot. */
+ get console(): BushLogger;
on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean;
@@ -126,72 +157,77 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
/**
* Whether or not the client is ready.
*/
- public customReady = false;
+ public override customReady = false;
/**
* Stats for the client.
*/
- public stats: BushStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n };
+ public override stats: BushStats = { cpu: undefined, commandsUsed: 0n, slashCommandsUsed: 0n };
/**
* The handler for the bot's listeners.
*/
- public listenerHandler: BushListenerHandler;
+ public override listenerHandler: BushListenerHandler;
/**
* The handler for the bot's command inhibitors.
*/
- public inhibitorHandler: BushInhibitorHandler;
+ public override inhibitorHandler: BushInhibitorHandler;
/**
* The handler for the bot's commands.
*/
- public commandHandler: BushCommandHandler;
+ public override commandHandler: BushCommandHandler;
/**
* The handler for the bot's tasks.
*/
- public taskHandler: BushTaskHandler;
+ public override taskHandler: BushTaskHandler;
/**
* The handler for the bot's context menu commands.
*/
- public contextMenuCommandHandler: ContextMenuCommandHandler;
+ public override contextMenuCommandHandler: ContextMenuCommandHandler;
/**
* The database connection for this instance of the bot (production, beta, or development).
*/
- public instanceDB: SequelizeType;
+ public override instanceDB: SequelizeType;
/**
* The database connection that is shared between all instances of the bot.
*/
- public sharedDB: SequelizeType;
+ public override sharedDB: SequelizeType;
/**
* A custom logging system for the bot.
*/
- public logger = BushLogger;
+ public override logger: BushLogger;
/**
* Cached global and guild database data.
*/
- public cache = new BushCache();
+ public override cache = new BushCache();
/**
* Sentry error reporting for the bot.
*/
- public sentry!: typeof Sentry;
+ public override sentry!: typeof Sentry;
/**
* Manages most aspects of the highlight command
*/
- public highlightManager = new HighlightManager();
+ public override highlightManager = new HighlightManager();
/**
* The perspective api
*/
- public perspective: any;
+ public override perspective: any;
+
+ /**
+ * Client utilities.
+ */
+ public override utils: BushClientUtils;
/**
* @param config The configuration for the bot.
@@ -200,7 +236,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
/**
* The configuration for the client.
*/
- public config: Config
+ public override config: Config
) {
super({
ownerID: config.owners,
@@ -220,7 +256,8 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
patch(this);
this.token = config.token as If<Ready, string, string | null>;
- this.config = config;
+ this.logger = new BushLogger(this);
+ this.utils = new BushClientUtils(this);
/* =-=-= handlers =-=-= */
this.listenerHandler = new BushListenerHandler(this, {
@@ -320,7 +357,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
/**
* A custom logging system for the bot.
*/
- public get console(): typeof BushLogger {
+ public override get console(): BushLogger {
return this.logger;
}
@@ -474,7 +511,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
await this.highlightManager.syncCache();
await UpdateCacheTask.init(this);
void this.console.success('startup', `Successfully created <<cache>>.`, false);
- const stats = await UpdateStatsTask.init();
+ const stats = await UpdateStatsTask.init(this);
this.stats.commandsUsed = stats.commandsUsed;
this.stats.slashCommandsUsed = stats.slashCommandsUsed;
await this.login(this.token!);
@@ -500,7 +537,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
public override isSuperUser(user: UserResolvable): boolean {
const userID = this.users.resolveId(user)!;
- return client.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID);
+ return this.cache.shared.superUsers.includes(userID) || this.config.owners.includes(userID);
}
}
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 5fb4e06..414da09 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -34,6 +34,8 @@ import {
Message,
User,
type ApplicationCommandOptionChoiceData,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ type ApplicationCommandOptionType,
type PermissionResolvable,
type PermissionsString,
type Snowflake
@@ -93,7 +95,7 @@ interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt
/**
* Allows you to get a discord resolved object
*
- * ex. get the resolved member object when the type is `USER`
+ * ex. get the resolved member object when the type is {@link ApplicationCommandOptionType.User User}
*/
slashResolve?: SlashResolveType;
@@ -113,12 +115,12 @@ interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt
channelTypes?: AkairoApplicationCommandChannelOptionData['channelTypes'];
/**
- * The minimum value for an `INTEGER` or `NUMBER` option
+ * The minimum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option
*/
minValue?: number;
/**
- * The maximum value for an `INTEGER` or `NUMBER` option
+ * The maximum value for an {@link ApplicationCommandOptionType.Integer Integer} or {@link ApplicationCommandOptionType.Number Number} option
*/
maxValue?: number;
}
diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts
index f095356..da49af9 100644
--- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts
+++ b/src/lib/extensions/discord-akairo/BushCommandHandler.ts
@@ -1,4 +1,4 @@
-import { type BushClient, type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
+import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo';
import { type Collection, type Message, type PermissionsString } from 'discord.js';
@@ -28,13 +28,8 @@ export interface BushCommandHandlerEvents extends CommandHandlerEvents {
}
export class BushCommandHandler extends CommandHandler {
- public declare client: BushClient;
public declare modules: Collection<string, BushCommand>;
public declare categories: Collection<string, Category<string, BushCommand>>;
-
- public constructor(client: BushClient, options: CommandHandlerOptions) {
- super(client, options);
- }
}
export interface BushCommandHandler extends CommandHandler {
diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts
index b4e6797..be396cf 100644
--- a/src/lib/extensions/discord-akairo/BushInhibitor.ts
+++ b/src/lib/extensions/discord-akairo/BushInhibitor.ts
@@ -1,15 +1,15 @@
-import { type BushClient, type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
+import { type BushCommand, type CommandMessage, type SlashMessage } from '#lib';
import { Inhibitor } from 'discord-akairo';
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { Message } from 'discord.js';
export abstract class BushInhibitor extends Inhibitor {
- public declare client: BushClient;
-
/**
* Checks if message should be blocked.
* A return value of true will block the message.
* If returning a Promise, a resolved value of true will block the message.
*
- * **Note:** `all` type inhibitors do not have `message.util` defined.
+ * **Note:** `'all'` type inhibitors do not have {@link Message.util} defined.
*
* @param message - Message being handled.
* @param command - Command to check.
diff --git a/src/lib/extensions/discord.js/ExtendedGuild.ts b/src/lib/extensions/discord.js/ExtendedGuild.ts
index c199899..c58916c 100644
--- a/src/lib/extensions/discord.js/ExtendedGuild.ts
+++ b/src/lib/extensions/discord.js/ExtendedGuild.ts
@@ -41,7 +41,7 @@ import _ from 'lodash';
import * as Moderation from '../../common/util/Moderation.js';
import { Guild as GuildDB } from '../../models/instance/Guild.js';
import { ModLogType } from '../../models/instance/ModLog.js';
-import { addOrRemoveFromArray, resolveNonCachedUser } from '../../utils/BushUtils.js';
+import { addOrRemoveFromArray } from '../../utils/BushUtils.js';
declare module 'discord.js' {
export interface Guild {
@@ -187,7 +187,7 @@ export class ExtendedGuild extends Guild {
*/
public override async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> {
return (
- client.cache.guilds.get(this.id)?.[setting] ??
+ this.client.cache.guilds.get(this.id)?.[setting] ??
((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting]
);
}
@@ -206,8 +206,8 @@ export class ExtendedGuild extends Guild {
const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id });
const oldValue = row[setting] as GuildDB[K];
row[setting] = value;
- client.cache.guilds.set(this.id, row.toJSON() as GuildDB);
- client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator);
+ this.client.cache.guilds.set(this.id, row.toJSON() as GuildDB);
+ this.client.emit('bushUpdateSettings', setting, this, oldValue, row[setting], moderator);
return await row.save();
}
@@ -253,7 +253,7 @@ export class ExtendedGuild extends Guild {
* @param message The description of the error embed
*/
public override async error(title: string, message: string): Promise<void> {
- void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>'));
+ void this.client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>'));
void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: colors.error }] });
}
@@ -268,8 +268,8 @@ export class ExtendedGuild extends Guild {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const user = await resolveNonCachedUser(options.user);
- const moderator = client.users.resolve(options.moderator ?? client.user!);
+ const user = await this.client.utils.resolveNonCachedUser(options.user);
+ const moderator = this.client.users.resolve(options.moderator ?? this.client.user!);
if (!user || !moderator) return banResponse.CANNOT_RESOLVE_USER;
if ((await this.bans.fetch()).has(user.id)) return banResponse.ALREADY_BANNED;
@@ -277,6 +277,7 @@ export class ExtendedGuild extends Guild {
const ret = await (async () => {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
user: user,
moderator: moderator.id,
@@ -290,6 +291,7 @@ export class ExtendedGuild extends Guild {
// dm user
dmSuccessEvent = await Moderation.punishDM({
+ client: this.client,
modlog: modlog.id,
guild: this,
user: user,
@@ -310,6 +312,7 @@ export class ExtendedGuild extends Guild {
// add punishment entry so they can be unbanned later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'ban',
user: user,
guild: this,
@@ -323,7 +326,7 @@ export class ExtendedGuild extends Guild {
})();
if (!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret))
- client.emit(
+ this.client.emit(
'bushBan',
user,
moderator,
@@ -352,6 +355,7 @@ export class ExtendedGuild extends Guild {
const ret = await (async () => {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntrySimple({
+ client: this.client,
type: ModLogType.PERM_BAN,
user: options.user,
moderator: options.moderator,
@@ -365,6 +369,7 @@ export class ExtendedGuild extends Guild {
// dm user
if (this.members.cache.has(options.user)) {
dmSuccessEvent = await Moderation.punishDM({
+ client: this.client,
modlog: modlog.id,
guild: this,
user: options.user,
@@ -386,6 +391,7 @@ export class ExtendedGuild extends Guild {
// add punishment entry so they can be unbanned later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'ban',
user: options.user,
guild: this,
@@ -411,8 +417,8 @@ export class ExtendedGuild extends Guild {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const user = await resolveNonCachedUser(options.user);
- const moderator = client.users.resolve(options.moderator ?? client.user!);
+ const user = await this.client.utils.resolveNonCachedUser(options.user);
+ const moderator = this.client.users.resolve(options.moderator ?? this.client.user!);
if (!user || !moderator) return unbanResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -435,6 +441,7 @@ export class ExtendedGuild extends Guild {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.UNBAN,
user: user.id,
moderator: moderator.id,
@@ -447,6 +454,7 @@ export class ExtendedGuild extends Guild {
// remove punishment entry
const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
+ client: this.client,
type: 'ban',
user: user.id,
guild: this
@@ -455,6 +463,7 @@ export class ExtendedGuild extends Guild {
// dm user
dmSuccessEvent = await Moderation.punishDM({
+ client: this.client,
guild: this,
user: user,
punishment: 'unbanned',
@@ -470,7 +479,16 @@ export class ExtendedGuild extends Guild {
ret
)
)
- client.emit('bushUnban', user, moderator, this, options.reason ?? undefined, caseID!, dmSuccessEvent!, options.evidence);
+ this.client.emit(
+ 'bushUnban',
+ user,
+ moderator,
+ this,
+ options.reason ?? undefined,
+ caseID!,
+ dmSuccessEvent!,
+ options.evidence
+ );
return ret;
}
@@ -549,7 +567,7 @@ export class ExtendedGuild extends Guild {
else return `success: ${success.filter((c) => c === true).size}`;
})();
- client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all);
+ this.client.emit(options.unlock ? 'bushUnlockdown' : 'bushLockdown', moderator, options.reason, success, options.all);
return ret;
}
@@ -557,7 +575,7 @@ export class ExtendedGuild extends Guild {
if (!channel.isTextBased() || channel.isDMBased() || channel.guildId !== this.id || !this.members.me) return null;
if (!channel.permissionsFor(this.members.me).has('ManageWebhooks')) return null;
- const quote = new Message(client, rawQuote);
+ const quote = new Message(this.client, rawQuote);
const target = channel instanceof ThreadChannel ? channel.parent : channel;
if (!target) return null;
@@ -570,8 +588,8 @@ export class ExtendedGuild extends Guild {
if (!webhook)
webhook = await target
.createWebhook({
- name: `${client.user!.username} Quotes #${target.name}`,
- avatar: client.user!.displayAvatarURL({ size: 2048 }),
+ name: `${this.client.user!.username} Quotes #${target.name}`,
+ avatar: this.client.user!.displayAvatarURL({ size: 2048 }),
reason: 'Creating a webhook for quoting'
})
.catch(() => null);
diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
index ad29236..947f9cd 100644
--- a/src/lib/extensions/discord.js/ExtendedGuildMember.ts
+++ b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { BushClientEvents, formatError, Moderation, ModLogType, PunishmentTypeDM, resolveNonCachedUser, Time } from '#lib';
+import { BushClientEvents, formatError, Moderation, ModLogType, PunishmentTypeDM, Time } from '#lib';
import {
ChannelType,
GuildChannelResolvable,
@@ -129,6 +129,7 @@ export class ExtendedGuildMember extends GuildMember {
sendFooter = true
): Promise<boolean> {
return Moderation.punishDM({
+ client: this.client,
modlog,
guild: this.guild,
user: this,
@@ -148,13 +149,14 @@ export class ExtendedGuildMember extends GuildMember {
public override async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return { result: warnResponse.CANNOT_RESOLVE_USER, caseNum: null };
const ret = await (async (): Promise<{ result: WarnResponse; caseNum: number | null }> => {
// add modlog entry
const result = await Moderation.createModLogEntry(
{
+ client: this.client,
type: ModLogType.WARN,
user: this,
moderator: moderator.id,
@@ -178,7 +180,7 @@ export class ExtendedGuildMember extends GuildMember {
return { result: warnResponse.SUCCESS, caseNum: result.caseNum };
})();
if (!([warnResponse.MODLOG_ERROR] as const).includes(ret.result) && !options.silent)
- client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!);
+ this.client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!);
return ret;
}
@@ -195,12 +197,13 @@ export class ExtendedGuildMember extends GuildMember {
if (ifShouldAddRole !== true) return ifShouldAddRole;
let caseID: string | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return addRoleResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
if (options.addToModlog || options.duration) {
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: options.duration ? ModLogType.TEMP_PUNISHMENT_ROLE : ModLogType.PERM_PUNISHMENT_ROLE,
guild: this.guild,
moderator: moderator.id,
@@ -216,6 +219,7 @@ export class ExtendedGuildMember extends GuildMember {
if (options.addToModlog || options.duration) {
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'role',
user: this,
guild: this.guild,
@@ -239,7 +243,7 @@ export class ExtendedGuildMember extends GuildMember {
options.addToModlog &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushPunishRole',
this,
moderator,
@@ -266,12 +270,13 @@ export class ExtendedGuildMember extends GuildMember {
if (ifShouldAddRole !== true) return ifShouldAddRole;
let caseID: string | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return removeRoleResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
if (options.addToModlog) {
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.REMOVE_PUNISHMENT_ROLE,
guild: this.guild,
moderator: moderator.id,
@@ -285,6 +290,7 @@ export class ExtendedGuildMember extends GuildMember {
caseID = modlog.id;
const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
+ client: this.client,
type: 'role',
user: this,
guild: this.guild,
@@ -311,7 +317,7 @@ export class ExtendedGuildMember extends GuildMember {
options.addToModlog &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushPunishRoleRemove',
this,
moderator,
@@ -362,7 +368,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return muteResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -370,14 +376,15 @@ export class ExtendedGuildMember extends GuildMember {
const muteSuccess = await this.roles
.add(muteRole, `[Mute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
.catch(async (e) => {
- await client.console.warn('muteRoleAddError', e);
- client.console.debug(e);
+ await this.client.console.warn('muteRoleAddError', e);
+ this.client.console.debug(e);
return false;
});
if (!muteSuccess) return muteResponse.ACTION_ERROR;
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: options.duration ? ModLogType.TEMP_MUTE : ModLogType.PERM_MUTE,
user: this,
moderator: moderator.id,
@@ -393,6 +400,7 @@ export class ExtendedGuildMember extends GuildMember {
// add punishment entry so they can be unmuted later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'mute',
user: this,
guild: this.guild,
@@ -416,7 +424,7 @@ export class ExtendedGuildMember extends GuildMember {
!([muteResponse.ACTION_ERROR, muteResponse.MODLOG_ERROR, muteResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushMute',
this,
moderator,
@@ -448,7 +456,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return unmuteResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -456,13 +464,14 @@ export class ExtendedGuildMember extends GuildMember {
const muteSuccess = await this.roles
.remove(muteRole, `[Unmute] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
.catch(async (e) => {
- await client.console.warn('muteRoleAddError', formatError(e, true));
+ await this.client.console.warn('muteRoleAddError', formatError(e, true));
return false;
});
if (!muteSuccess) return unmuteResponse.ACTION_ERROR;
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.UNMUTE,
user: this,
moderator: moderator.id,
@@ -477,6 +486,7 @@ export class ExtendedGuildMember extends GuildMember {
// remove mute entry
const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({
+ client: this.client,
type: 'mute',
user: this,
guild: this.guild
@@ -500,7 +510,7 @@ export class ExtendedGuildMember extends GuildMember {
).includes(ret) &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushUnmute',
this,
moderator,
@@ -526,11 +536,12 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return kickResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.KICK,
user: this,
moderator: moderator.id,
@@ -554,7 +565,7 @@ export class ExtendedGuildMember extends GuildMember {
return kickResponse.SUCCESS;
})();
if (!([kickResponse.ACTION_ERROR, kickResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- client.emit(
+ this.client.emit(
'bushKick',
this,
moderator,
@@ -580,7 +591,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return banResponse.CANNOT_RESOLVE_USER;
// ignore result, they should still be banned even if their mute cannot be removed
@@ -593,6 +604,7 @@ export class ExtendedGuildMember extends GuildMember {
const ret = await (async () => {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
user: this,
moderator: moderator.id,
@@ -620,6 +632,7 @@ export class ExtendedGuildMember extends GuildMember {
// add punishment entry so they can be unbanned later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'ban',
user: this,
guild: this.guild,
@@ -635,7 +648,7 @@ export class ExtendedGuildMember extends GuildMember {
!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushBan',
this,
moderator,
@@ -663,7 +676,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return blockResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -677,6 +690,7 @@ export class ExtendedGuildMember extends GuildMember {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: options.duration ? ModLogType.TEMP_CHANNEL_BLOCK : ModLogType.PERM_CHANNEL_BLOCK,
user: this,
moderator: moderator.id,
@@ -690,6 +704,7 @@ export class ExtendedGuildMember extends GuildMember {
// add punishment entry so they can be unblocked later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
+ client: this.client,
type: 'block',
user: this,
guild: this.guild,
@@ -703,6 +718,7 @@ export class ExtendedGuildMember extends GuildMember {
const dmSuccess = options.silent
? null
: await Moderation.punishDM({
+ client: this.client,
punishment: 'blocked',
reason: options.reason ?? undefined,
duration: options.duration ?? 0,
@@ -724,7 +740,7 @@ export class ExtendedGuildMember extends GuildMember {
) &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushBlock',
this,
moderator,
@@ -754,7 +770,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return unblockResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -768,6 +784,7 @@ export class ExtendedGuildMember extends GuildMember {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.CHANNEL_UNBLOCK,
user: this,
moderator: moderator.id,
@@ -781,6 +798,7 @@ export class ExtendedGuildMember extends GuildMember {
// remove punishment entry
const punishmentEntrySuccess = await Moderation.removePunishmentEntry({
+ client: this.client,
type: 'block',
user: this,
guild: this.guild,
@@ -792,6 +810,7 @@ export class ExtendedGuildMember extends GuildMember {
const dmSuccess = options.silent
? null
: await Moderation.punishDM({
+ client: this.client,
punishment: 'unblocked',
reason: options.reason ?? undefined,
guild: this.guild,
@@ -812,7 +831,7 @@ export class ExtendedGuildMember extends GuildMember {
!([unblockResponse.ACTION_ERROR, unblockResponse.MODLOG_ERROR, unblockResponse.ACTION_ERROR] as const).includes(ret) &&
!options.silent
)
- client.emit(
+ this.client.emit(
'bushUnblock',
this,
moderator,
@@ -839,7 +858,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return timeoutResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -852,6 +871,7 @@ export class ExtendedGuildMember extends GuildMember {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.TIMEOUT,
user: this,
moderator: moderator.id,
@@ -876,7 +896,7 @@ export class ExtendedGuildMember extends GuildMember {
})();
if (!([timeoutResponse.ACTION_ERROR, timeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- client.emit(
+ this.client.emit(
'bushTimeout',
this,
moderator,
@@ -901,7 +921,7 @@ export class ExtendedGuildMember extends GuildMember {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
- const moderator = await resolveNonCachedUser(options.moderator ?? this.guild.members.me);
+ const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me);
if (!moderator) return removeTimeoutResponse.CANNOT_RESOLVE_USER;
const ret = await (async () => {
@@ -913,6 +933,7 @@ export class ExtendedGuildMember extends GuildMember {
// add modlog entry
const { log: modlog } = await Moderation.createModLogEntry({
+ client: this.client,
type: ModLogType.REMOVE_TIMEOUT,
user: this,
moderator: moderator.id,
@@ -936,7 +957,7 @@ export class ExtendedGuildMember extends GuildMember {
})();
if (!([removeTimeoutResponse.ACTION_ERROR, removeTimeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent)
- client.emit(
+ this.client.emit(
'bushRemoveTimeout',
this,
moderator,
@@ -953,14 +974,14 @@ export class ExtendedGuildMember extends GuildMember {
* Whether or not the user is an owner of the bot.
*/
public override isOwner(): boolean {
- return client.isOwner(this);
+ return this.client.isOwner(this);
}
/**
* Whether or not the user is a super user of the bot.
*/
public override isSuperUser(): boolean {
- return client.isSuperUser(this);
+ return this.client.isSuperUser(this);
}
}
diff --git a/src/lib/extensions/discord.js/ExtendedMessage.ts b/src/lib/extensions/discord.js/ExtendedMessage.ts
index 4431077..0d8ce37 100644
--- a/src/lib/extensions/discord.js/ExtendedMessage.ts
+++ b/src/lib/extensions/discord.js/ExtendedMessage.ts
@@ -7,6 +7,6 @@ export class ExtendedMessage<Cached extends boolean = boolean> extends Message<C
public constructor(client_: Client, data: RawMessageData) {
super(client_, data);
- this.util = new CommandUtil(client.commandHandler, this);
+ this.util = new CommandUtil(this.client.commandHandler, this);
}
}
diff --git a/src/lib/extensions/discord.js/ExtendedUser.ts b/src/lib/extensions/discord.js/ExtendedUser.ts
index 556ab85..23de523 100644
--- a/src/lib/extensions/discord.js/ExtendedUser.ts
+++ b/src/lib/extensions/discord.js/ExtendedUser.ts
@@ -23,13 +23,13 @@ export class ExtendedUser extends User {
* Indicates whether the user is an owner of the bot.
*/
public override isOwner(): boolean {
- return client.isOwner(this);
+ return this.client.isOwner(this);
}
/**
* Indicates whether the user is a superuser of the bot.
*/
public override isSuperUser(): boolean {
- return client.isSuperUser(this);
+ return this.client.isSuperUser(this);
}
}
diff --git a/src/lib/extensions/global.ts b/src/lib/extensions/global.ts
index d9cfaec..a9020d7 100644
--- a/src/lib/extensions/global.ts
+++ b/src/lib/extensions/global.ts
@@ -1,11 +1,5 @@
/* eslint-disable no-var */
-import type { BushClient } from '#lib';
declare global {
- /**
- * The bushbot client.
- */
- var client: BushClient;
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ReadonlyArray<T> {
includes<S, R extends `${Extract<S, string>}`>(
@@ -15,3 +9,5 @@ declare global {
): searchElement is R & S;
}
}
+
+export {};
diff --git a/src/lib/models/instance/Guild.ts b/src/lib/models/instance/Guild.ts
index 49bf822..7a19b72 100644
--- a/src/lib/models/instance/Guild.ts
+++ b/src/lib/models/instance/Guild.ts
@@ -199,12 +199,14 @@ const asGuildSetting = <T>(et: { [K in keyof T]: PartialBy<GuildSetting, 'config
return et as { [K in keyof T]: GuildSetting };
};
+const { default: config } = await import('../../../../config/options.js');
+
export const guildSettingsObj = asGuildSetting({
prefix: {
name: 'Prefix',
description: 'The phrase required to trigger text commands in this server.',
type: 'string',
- replaceNullWith: () => client.config.prefix
+ replaceNullWith: () => config.prefix
},
autoPublishChannels: {
name: 'Auto Publish Channels',
diff --git a/src/lib/utils/BushClientUtils.ts b/src/lib/utils/BushClientUtils.ts
new file mode 100644
index 0000000..44a08ef
--- /dev/null
+++ b/src/lib/utils/BushClientUtils.ts
@@ -0,0 +1,480 @@
+import assert from 'assert';
+import {
+ cleanCodeBlockContent,
+ escapeCodeBlock,
+ GuildMember,
+ Message,
+ Routes,
+ TextChannel,
+ ThreadMember,
+ User,
+ type APIMessage,
+ type Client,
+ type Snowflake,
+ type UserResolvable
+} from 'discord.js';
+import got from 'got';
+import _ from 'lodash';
+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 {
+ const mapping = {
+ token: 'Main Token',
+ devToken: 'Dev Token',
+ betaToken: 'Beta Token',
+ hypixelApiKey: 'Hypixel Api Key',
+ wolframAlphaAppId: 'Wolfram|Alpha App ID',
+ dbPassword: 'Database Password'
+ };
+ return mapping[key as keyof typeof mapping] || 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
+ ];
+ 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;
+ }
+
+ /**
+ * Gets a a configured channel as a TextChannel.
+ * @channel The channel to retrieve.
+ */
+ public async getConfigChannel(channel: keyof Client['config']['channels']): Promise<TextChannel> {
+ return (await this.client.channels.fetch(this.client.config.channels[channel])) as unknown as TextChannel;
+ }
+}
+
+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/BushLogger.ts b/src/lib/utils/BushLogger.ts
index 7d42574..1be58a4 100644
--- a/src/lib/utils/BushLogger.ts
+++ b/src/lib/utils/BushLogger.ts
@@ -1,11 +1,11 @@
import chalk from 'chalk';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js';
+import { Client, EmbedBuilder, escapeMarkdown, PartialTextBasedChannelFields, type Message } from 'discord.js';
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 { getConfigChannel, inspect } from './BushUtils.js';
+import { inspect } from './BushUtils.js';
let REPL: REPLServer;
let replGone = false;
@@ -131,7 +131,14 @@ function getTimeStamp(): string {
/**
* Custom logging utility for the bot.
*/
-export default {
+export class BushLogger {
+ public constructor(
+ /**
+ * The client.
+ */
+ public client: Client
+ ) {}
+
/**
* Logs information. Highlight information by surrounding it in `<<>>`.
* @param header The header displayed before the content, displayed in cyan.
@@ -139,27 +146,27 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to false.
* @param depth The depth the content will inspected. Defaults to 0.
*/
- get log() {
+ 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.
*/
- async channelLog(message: SendMessageType): Promise<Message | null> {
- const channel = await getConfigChannel('log');
+ public async channelLog(message: SendMessageType): Promise<Message | null> {
+ const channel = await this.client.utils.getConfigChannel('log');
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.
*/
- async channelError(message: SendMessageType): Promise<Message | null> {
- const channel = await getConfigChannel('error');
+ public async channelError(message: SendMessageType): Promise<Message | null> {
+ const channel = await this.client.utils.getConfigChannel('error');
if (!channel) {
void this.error(
'BushLogger',
@@ -171,27 +178,27 @@ export default {
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`.
*/
- debug(content: any, depth = 0): void {
- if (!client.config.isDevelopment) return;
+ 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.
*/
- debugRaw(...content: any): void {
- if (!client.config.isDevelopment) return;
+ 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 `<<>>`.
@@ -200,8 +207,8 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to `false`.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise<void> {
- if (!client.config.logging.verbose) return;
+ 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;
@@ -210,7 +217,7 @@ export default {
.setColor(colors.gray)
.setTimestamp();
await this.channelLog({ embeds: [embed] });
- },
+ }
/**
* Logs very verbose information. Highlight information by surrounding it in `<<>>`.
@@ -218,23 +225,23 @@ export default {
* @param content The content to log, highlights displayed in bright black.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async superVerbose(header: string, content: any, depth = 0): Promise<void> {
- if (!client.config.logging.verbose) return;
+ 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.
*/
- async superVerboseRaw(header: string, ...content: any[]): Promise<void> {
- if (!client.config.logging.verbose) return;
+ 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 `<<>>`.
@@ -243,8 +250,8 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to `false`.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async info(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
- if (!client.config.logging.info) return;
+ 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;
@@ -253,7 +260,7 @@ export default {
.setColor(colors.info)
.setTimestamp();
await this.channelLog({ embeds: [embed] });
- },
+ }
/**
* Logs warnings. Highlight information by surrounding it in `<<>>`.
@@ -262,7 +269,7 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to `false`.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async warn(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
+ 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')}`
@@ -274,7 +281,7 @@ export default {
.setColor(colors.warn)
.setTimestamp();
await this.channelError({ embeds: [embed] });
- },
+ }
/**
* Logs errors. Highlight information by surrounding it in `<<>>`.
@@ -283,7 +290,7 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to `false`.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async error(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
+ 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')}`
@@ -295,7 +302,7 @@ export default {
.setTimestamp();
await this.channelError({ embeds: [embed] });
return;
- },
+ }
/**
* Logs successes. Highlight information by surrounding it in `<<>>`.
@@ -304,7 +311,7 @@ export default {
* @param sendChannel Should this also be logged to discord? Defaults to `false`.
* @param depth The depth the content will inspected. Defaults to `0`.
*/
- async success(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
+ 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')}`
@@ -316,6 +323,6 @@ export default {
.setTimestamp();
await this.channelLog({ embeds: [embed] }).catch(() => {});
}
-};
+}
/** @typedef {PartialTextBasedChannelFields} vscodeDontDeleteMyImportTy */
diff --git a/src/lib/utils/BushUtils.ts b/src/lib/utils/BushUtils.ts
index 8a84d80..a6463cf 100644
--- a/src/lib/utils/BushUtils.ts
+++ b/src/lib/utils/BushUtils.ts
@@ -1,22 +1,12 @@
import {
Arg,
BushClient,
- CodeBlockLang,
CommandMessage,
- emojis,
- Global,
- Pronoun,
- pronounMapping,
- regex,
- Shared,
SlashEditMessageType,
SlashSendMessageType,
timeUnits,
type BaseBushArgumentType,
type BushInspectOptions,
- type GlobalCache,
- type PronounCode,
- type SharedCache,
type SlashMessage
} from '#lib';
import { humanizeDuration as humanizeDurationMod } from '@notenoughupdates/humanize-duration';
@@ -25,59 +15,25 @@ import { exec } from 'child_process';
import deepLock from 'deep-lock';
import { Util as AkairoUtil } from 'discord-akairo';
import {
- cleanCodeBlockContent,
Constants as DiscordConstants,
EmbedBuilder,
- escapeCodeBlock,
- GuildMember,
Message,
OAuth2Scopes,
PermissionFlagsBits,
PermissionsBitField,
- Routes,
- ThreadMember,
- User,
- UserResolvable,
type APIEmbed,
type APIMessage,
type CommandInteraction,
type InteractionReplyOptions,
- type PermissionsString,
- type Snowflake,
- type TextChannel
+ type PermissionsString
} from 'discord.js';
import got from 'got';
-import _ from 'lodash';
import { inspect as inspectUtil, promisify } from 'util';
-import CommandErrorListener from '../../listeners/commands/commandError.js';
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] };
/**
- * The hastebin urls used to post to hastebin, attempts to post in order
- */
-const 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'
-];
-
-/**
- * Maps an array of user ids to user objects.
- * @param ids The list of IDs to map
- * @returns The list of users mapped
- */
-export async function mapIDs(ids: Snowflake[]): Promise<User[]> {
- return await Promise.all(ids.map((id) => client.users.fetch(id)));
-}
-
-/**
* Capitalizes the first letter of the given text
* @param text The text to capitalize
* @returns The capitalized text
@@ -96,60 +52,6 @@ export async function shell(command: string): Promise<{ stdout: string; stderr:
}
/**
- * Posts text to hastebin
- * @param content The text to post
- * @returns The url of the posted text
- */
-export async function haste(content: string, substr = false): Promise<HasteResults> {
- let isSubstr = false;
- if (content.length > 400_000 && !substr) {
- void 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 hasteURLs) {
- try {
- 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}`);
- }
- }
- 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
- */
-export async function resolveUserAsync(text: string): Promise<User | null> {
- const idReg = /\d{17,19}/;
- const idMatch = text.match(idReg);
- if (idMatch) {
- try {
- return await client.users.fetch(text as Snowflake);
- } catch {
- // pass
- }
- }
- const mentionReg = /<@!?(?<id>\d{17,19})>/;
- const mentionMatch = text.match(mentionReg);
- if (mentionMatch) {
- try {
- return await client.users.fetch(mentionMatch.groups!.id as Snowflake);
- } catch {
- // pass
- }
- }
- const user = client.users.cache.find((u) => u.username === text);
- if (user) return user;
- return null;
-}
-
-/**
* Appends the correct ordinal to the given number
* @param n The number to append an ordinal to
* @returns The number with the ordinal
@@ -185,46 +87,6 @@ export async function mcUUID(username: string, dashed = false): Promise<string>
}
/**
- * 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
- */
-export async function 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 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 client.console.warn(`codeblockError`, `Required Length: ${length}. Actual Length: ${code3.length}`, true);
- void client.console.warn(`codeblockError`, code3, true);
- throw new Error('code too long');
- }
- return code3;
-}
-
-/**
* Generate defaults for {@link inspect}.
* @param options The options to create defaults with.
* @returns The default options combined with the specified options.
@@ -246,44 +108,6 @@ function getDefaultInspectOptions(options?: BushInspectOptions): BushInspectOpti
}
/**
- * 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.
- */
-function mapCredential(key: string): string {
- const mapping = {
- token: 'Main Token',
- devToken: 'Dev Token',
- betaToken: 'Beta Token',
- hypixelApiKey: 'Hypixel Api Key',
- wolframAlphaAppId: 'Wolfram|Alpha App ID',
- dbPassword: 'Database Password'
- };
- return mapping[key as keyof typeof mapping] || key;
-}
-
-/**
- * Redacts credentials from a string.
- * @param text The text to redact credentials from.
- * @returns The redacted text.
- */
-export function redact(text: string) {
- for (const credentialName in { ...client.config.credentials, dbPassword: client.config.db.password }) {
- const credential = { ...client.config.credentials, dbPassword: client.config.db.password }[
- credentialName as keyof typeof client.config.credentials
- ];
- const replacement = 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;
-}
-
-/**
* 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.
@@ -298,51 +122,6 @@ export function inspect(object: any, options?: BushInspectOptions): string {
}
/**
- * 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.
- */
-export async function inspectCleanRedactCodeblock(
- input: any,
- language?: CodeBlockLang | '',
- inspectOptions?: BushInspectOptions,
- length = 1024
-) {
- input = inspect(input, inspectOptions ?? undefined);
- if (inspectOptions) inspectOptions.inspectStrings = undefined;
- input = cleanCodeBlockContent(input);
- input = redact(input);
- return 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}.
- */
-export async function inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions): Promise<HasteResults> {
- input = inspect(input, inspectOptions ?? undefined);
- input = redact(input);
- return 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.
- */
-export function inspectAndRedact(input: any, inspectOptions?: BushInspectOptions): string {
- input = inspect(input, inspectOptions ?? undefined);
- return redact(input);
-}
-
-/**
* Responds to a slash command interaction.
* @param interaction The interaction to respond to.
* @param responseOptions The options for the response.
@@ -363,14 +142,6 @@ export async function slashRespond(
}
/**
- * Gets a a configured channel as a TextChannel.
- * @channel The channel to retrieve.
- */
-export async function getConfigChannel(channel: keyof typeof client['config']['channels']): Promise<TextChannel> {
- return (await client.channels.fetch(client.config.channels[channel])) as unknown as TextChannel;
-}
-
-/**
* Takes an array and combines the elements using the supplied conjunction.
* @param array The array to combine.
* @param conjunction The conjunction to use.
@@ -392,93 +163,6 @@ export function oxford(array: string[], conjunction: string, ifEmpty?: string):
}
/**
- * Get the global cache.
- */
-export function getGlobal(): GlobalCache;
-/**
- * Get a key from the global cache.
- * @param key The key to get in the global cache.
- */
-export function getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K];
-export function getGlobal(key?: keyof GlobalCache) {
- return key ? client.cache.global[key] : client.cache.global;
-}
-
-export function getShared(): SharedCache;
-export function getShared<K extends keyof SharedCache>(key: K): SharedCache[K];
-export function getShared(key?: keyof SharedCache) {
- return key ? client.cache.shared[key] : 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.
- */
-export async function insertOrRemoveFromGlobal<K extends keyof typeof client['cache']['global']>(
- action: 'add' | 'remove',
- 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 }));
- const oldValue: any[] = row[key];
- const newValue = addOrRemoveFromArray(action, oldValue, value);
- row[key] = newValue;
- client.cache.global[key] = newValue;
- return await row.save().catch((e) => 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.
- */
-export async function insertOrRemoveFromShared<
- K extends Exclude<keyof typeof client['cache']['shared'], 'badWords' | 'autoBanCode'>
->(action: 'add' | 'remove', key: K, value: typeof 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;
- client.cache.shared[key] = newValue;
- return await row.save().catch((e) => 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.
- */
-export async function 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) => 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.
- */
-export async function setShared<K extends Exclude<keyof typeof client['cache']['shared'], 'badWords' | 'autoBanCode'>>(
- key: K,
- value: typeof client['cache']['shared'][K]
-): Promise<Shared | void> {
- const row = (await Shared.findByPk(0)) ?? (await Shared.create());
- row[key] = value;
- client.cache.shared[key] = value;
- return await row.save().catch((e) => handleError('setShared', 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.
@@ -643,58 +327,6 @@ export function hexToRgb(hex: string): string {
export const sleep = promisify(setTimeout);
/**
- * Send a message in the error logging channel and console for an error.
- * @param context
- * @param error
- */
-export async function handleError(context: string, error: Error) {
- await client.console.error(_.camelCase(context), `An error occurred:\n${formatError(error, false)}`, false);
- await client.console.channelError({
- embeds: await CommandErrorListener.generateErrorEmbed({ 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.
- */
-export async function 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 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.
- */
-export async function getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
- const _user = await 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!]!;
-}
-
-/**
* 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.
@@ -763,31 +395,6 @@ export function getSymbols(obj: Record<string, any>): symbol[] {
}
/**
- * Uploads an image to imgur.
- * @param image The image to upload.
- * @returns The url of the imgur.
- */
-export async function uploadImageToImgur(image: string) {
- const clientId = 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;
-}
-
-/**
* 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.
@@ -843,15 +450,6 @@ export function clientSendAndPermCheck(
return missing.length ? missing : null;
}
-/**
- * Gets the prefix based off of the message.
- * @param message The message to get the prefix from.
- * @returns The prefix.
- */
-export function prefix(message: CommandMessage | SlashMessage): string {
- return message.util.isSlash ? '/' : client.config.isDevelopment ? 'dev ' : message.util.parsed?.prefix ?? client.config.prefix;
-}
-
export { deepLock as deepFreeze };
export { Arg as arg };
export { Format as format };
@@ -950,48 +548,6 @@ export function overflowEmbed(embed: Omit<APIEmbed, 'description'>, lines: strin
return embeds;
}
-export async function 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;
-}
-
-export async function resolveMessagesFromLinks(content: string): Promise<APIMessage[]> {
- const res: APIMessage[] = [];
-
- const links = await resolveMessageLinks(content);
- if (!links.length) return [];
-
- for (const { guild_id, channel_id, message_id } of links) {
- const guild = 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 client.rest.get(Routes.channelMessage(channel_id, message_id)).catch(() => null)) as APIMessage | null;
- if (!message) continue;
-
- res.push(message);
- }
-
- return res;
-}
-
/**
* Formats an error into a string.
* @param error The error to format.
@@ -1011,10 +567,6 @@ export function formatError(error: Error | any, colors = false): string {
return error.stack;
}
-interface HastebinRes {
- key: string;
-}
-
export interface UuidRes {
uuid: string;
username: string;
@@ -1034,11 +586,6 @@ export interface UuidRes {
created_at: string;
}
-export interface HasteResults {
- url?: string;
- error?: 'content too long' | 'substr' | 'unable to post';
-}
-
export interface ParsedDuration {
duration: number | null;
content: string | null;
@@ -1050,9 +597,3 @@ export interface ParsedDurationRes {
}
export type TimestampStyle = 't' | 'T' | 'd' | 'D' | 'f' | 'F' | 'R';
-
-export interface MessageLinkParts {
- guild_id: Snowflake;
- channel_id: Snowflake;
- message_id: Snowflake;
-}