aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json2
-rw-r--r--src/bot.ts6
-rw-r--r--src/commands/config/blacklist.ts105
-rw-r--r--src/commands/config/config.ts2
-rw-r--r--src/commands/config/disable.ts23
-rw-r--r--src/commands/config/log.ts7
-rw-r--r--src/commands/info/snowflake.ts5
-rw-r--r--src/commands/info/userInfo.ts34
-rw-r--r--src/commands/moderation/role.ts2
-rw-r--r--src/commands/utilities/suicide.ts6
-rw-r--r--src/context-menu-commands/user/userInfo.ts26
-rw-r--r--src/inhibitors/blacklist/userGlobalBlacklist.ts3
-rw-r--r--src/lib/common/AutoMod.ts1
-rw-r--r--src/lib/common/ButtonPaginator.ts3
-rw-r--r--src/lib/common/DeleteButton.ts3
-rw-r--r--src/lib/common/Moderation.ts166
-rw-r--r--src/lib/common/util/Arg.ts56
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts195
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts102
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts188
-rw-r--r--src/lib/extensions/discord-akairo/BushInhibitor.ts7
-rw-r--r--src/lib/extensions/discord-akairo/BushSlashMessage.ts2
-rw-r--r--src/lib/extensions/discord.js/BushActivity.ts3
-rw-r--r--src/lib/extensions/discord.js/BushApplicationCommand.ts3
-rw-r--r--src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts101
-rw-r--r--src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts123
-rw-r--r--src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts8
-rw-r--r--src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts13
-rw-r--r--src/lib/extensions/discord.js/BushButtonInteraction.ts7
-rw-r--r--src/lib/extensions/discord.js/BushCategoryChannel.ts7
-rw-r--r--src/lib/extensions/discord.js/BushChannel.d.ts14
-rw-r--r--src/lib/extensions/discord.js/BushChannelManager.d.ts20
-rw-r--r--src/lib/extensions/discord.js/BushClientEvents.d.ts29
-rw-r--r--src/lib/extensions/discord.js/BushClientUser.d.ts78
-rw-r--r--src/lib/extensions/discord.js/BushCommandInteraction.ts31
-rw-r--r--src/lib/extensions/discord.js/BushDMChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushEmoji.ts3
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts109
-rw-r--r--src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts89
-rw-r--r--src/lib/extensions/discord.js/BushGuildBan.d.ts3
-rw-r--r--src/lib/extensions/discord.js/BushGuildChannel.ts9
-rw-r--r--src/lib/extensions/discord.js/BushGuildEmoji.ts3
-rw-r--r--src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts36
-rw-r--r--src/lib/extensions/discord.js/BushGuildManager.d.ts16
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts289
-rw-r--r--src/lib/extensions/discord.js/BushGuildMemberManager.d.ts131
-rw-r--r--src/lib/extensions/discord.js/BushMessage.ts10
-rw-r--r--src/lib/extensions/discord.js/BushMessageManager.d.ts84
-rw-r--r--src/lib/extensions/discord.js/BushMessageReaction.ts3
-rw-r--r--src/lib/extensions/discord.js/BushNewsChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushPresence.ts3
-rw-r--r--src/lib/extensions/discord.js/BushReactionEmoji.ts5
-rw-r--r--src/lib/extensions/discord.js/BushRole.ts3
-rw-r--r--src/lib/extensions/discord.js/BushSelectMenuInteraction.ts7
-rw-r--r--src/lib/extensions/discord.js/BushStageChannel.ts5
-rw-r--r--src/lib/extensions/discord.js/BushStageInstance.ts3
-rw-r--r--src/lib/extensions/discord.js/BushStoreChannel.ts4
-rw-r--r--src/lib/extensions/discord.js/BushTextChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushThreadChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushThreadManager.d.ts57
-rw-r--r--src/lib/extensions/discord.js/BushThreadMember.ts3
-rw-r--r--src/lib/extensions/discord.js/BushThreadMemberManager.d.ts31
-rw-r--r--src/lib/extensions/discord.js/BushUser.ts9
-rw-r--r--src/lib/extensions/discord.js/BushUserManager.d.ts59
-rw-r--r--src/lib/extensions/discord.js/BushVoiceChannel.ts3
-rw-r--r--src/lib/extensions/discord.js/BushVoiceState.ts9
-rw-r--r--src/lib/extensions/global.d.ts7
-rw-r--r--src/lib/utils/BushCache.ts4
-rw-r--r--src/lib/utils/BushConstants.ts4
-rw-r--r--src/lib/utils/BushLogger.ts118
-rw-r--r--src/listeners/client/dcjsDebug.ts15
-rw-r--r--src/listeners/client/dcjsError.ts15
-rw-r--r--src/listeners/client/dcjsWarn.ts15
-rw-r--r--src/listeners/commands/commandError.ts8
-rw-r--r--src/listeners/commands/commandStarted.ts7
-rw-r--r--src/listeners/message/autoThread.ts9
-rw-r--r--src/listeners/other/warning.ts2
-rw-r--r--tsconfig.json5
-rw-r--r--yarn.lock99
80 files changed, 2133 insertions, 557 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index bf53657..b6f5614 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -27,7 +27,7 @@
"editor.insertSpaces": false,
"editor.wordWrap": "on",
"editor.tabSize": 2,
- "prettier.configPath": "package.json",
+ "prettier.configPath": ".prettierrc.json",
"prettier.prettierPath": "node_modules/prettier",
"prettier.withNodeModules": true,
"prettier.useEditorConfig": false,
diff --git a/src/bot.ts b/src/bot.ts
index bbef018..473ee27 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -6,5 +6,7 @@ import { Sentry } from './lib/common/Sentry.js';
import { BushClient } from './lib/index.js';
new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd());
-BushClient.init();
-void new BushClient(config).start();
+BushClient.extendStructures();
+const client = new BushClient(config);
+await client.init();
+if (!process.argv.includes('dry')) await client.start();
diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts
index da4ad18..a6e6a3d 100644
--- a/src/commands/config/blacklist.ts
+++ b/src/commands/config/blacklist.ts
@@ -1,6 +1,5 @@
-import { AllowedMentions, BushCommand, Global, type BushMessage, type BushSlashMessage } from '#lib';
-import { GuildTextBasedChannels } from 'discord-akairo';
-import { User } from 'discord.js';
+import { AllowedMentions, BushCommand, type BushMessage, type BushSlashMessage } from '#lib';
+import { GuildTextBasedChannel, User } from 'discord.js';
export default class BlacklistCommand extends BushCommand {
public constructor() {
@@ -52,10 +51,10 @@ export default class BlacklistCommand extends BushCommand {
public override async exec(
message: BushMessage | BushSlashMessage,
- args: { action: 'blacklist' | 'unblacklist'; target: GuildTextBasedChannels | User | string; global: boolean }
+ args: { action?: 'blacklist' | 'unblacklist'; target: GuildTextBasedChannel | User | string; global: boolean }
) {
let action: 'blacklist' | 'unblacklist' | 'toggle' =
- args.action ?? (message?.util?.parsed?.alias as 'blacklist' | 'unblacklist') ?? 'toggle';
+ args.action ?? (message?.util?.parsed?.alias as 'blacklist' | 'unblacklist' | undefined) ?? 'toggle';
const global = args.global && message.author.isOwner();
const target =
typeof args.target === 'string'
@@ -64,65 +63,43 @@ export default class BlacklistCommand extends BushCommand {
if (!target) return await message.util.reply(`${util.emojis.error} Choose a valid channel or user.`);
const targetID = target.id;
- if (global) {
- if ((action as 'blacklist' | 'unblacklist' | 'toggle') === 'toggle') {
- const globalDB =
- (await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment }));
- const blacklistedUsers = globalDB.blacklistedUsers;
- const blacklistedChannels = globalDB.blacklistedChannels;
- action = blacklistedUsers.includes(targetID) || blacklistedChannels.includes(targetID) ? 'unblacklist' : 'blacklist';
- }
- const success = await util
- .insertOrRemoveFromGlobal(
- action === 'blacklist' ? 'add' : 'remove',
- target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels',
- targetID
- )
- .catch(() => false);
- if (!success)
- return await message.util.reply({
- content: `${util.emojis.error} There was an error globally ${action}ing ${util.format.input(
- target instanceof User ? target.tag : target.name
- )}.`,
- allowedMentions: AllowedMentions.none()
- });
- else
- return await message.util.reply({
- content: `${util.emojis.success} Successfully ${action}ed ${util.format.input(
- target instanceof User ? target.tag : target.name
- )} globally.`,
- allowedMentions: AllowedMentions.none()
- });
- // guild disable
- } else {
- if (!message.guild) return await message.util.reply(`${util.emojis.error} You have to be in a guild to disable commands.`);
- const blacklistedChannels = (await message.guild.getSetting('blacklistedChannels')) ?? [];
- const blacklistedUsers = (await message.guild.getSetting('blacklistedUsers')) ?? [];
- if ((action as 'blacklist' | 'unblacklist' | 'toggle') === 'toggle') {
- action = blacklistedChannels.includes(targetID) ?? blacklistedUsers.includes(targetID) ? 'unblacklist' : 'blacklist';
- }
- const newValue = util.addOrRemoveFromArray(
- action === 'blacklist' ? 'add' : 'remove',
- target instanceof User ? blacklistedUsers : blacklistedChannels,
- targetID
- );
- const success = await message.guild
- .setSetting(target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels', newValue, message.member!)
- .catch(() => false);
- if (!success)
- return await message.util.reply({
- content: `${util.emojis.error} There was an error ${action}ing ${util.format.input(
- target instanceof User ? target.tag : target.name
- )}.`,
- allowedMentions: AllowedMentions.none()
- });
- else
- return await message.util.reply({
- content: `${util.emojis.success} Successfully ${action}ed ${util.format.input(
- target instanceof User ? target.tag : target.name
- )}.`,
- allowedMentions: AllowedMentions.none()
- });
+ if (!message.guild && global)
+ return await message.util.reply(`${util.emojis.error} You have to be in a guild to disable commands.`);
+ const blacklistedUsers = global
+ ? util.getGlobal('blacklistedUsers')
+ : (await message.guild!.getSetting('blacklistedChannels')) ?? [];
+ const blacklistedChannels = global
+ ? util.getGlobal('blacklistedChannels')
+ : (await message.guild!.getSetting('blacklistedUsers')) ?? [];
+ if (action === 'toggle') {
+ action = blacklistedUsers.includes(targetID) || blacklistedChannels.includes(targetID) ? 'unblacklist' : 'blacklist';
}
+ const newValue = util.addOrRemoveFromArray(
+ action === 'blacklist' ? 'add' : 'remove',
+ target instanceof User ? blacklistedUsers : blacklistedChannels,
+ targetID
+ );
+
+ const key = target instanceof User ? 'blacklistedUsers' : 'blacklistedChannels';
+
+ const success = await (global
+ ? util.setGlobal(key, newValue)
+ : message.guild!.setSetting(key, newValue, message.member!)
+ ).catch(() => false);
+
+ if (!success)
+ return await message.util.reply({
+ content: `${util.emojis.error} There was an error${global ? ' globally' : ''} ${action}ing ${util.format.input(
+ target instanceof User ? target.tag : target.name
+ )}.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ else
+ return await message.util.reply({
+ content: `${util.emojis.success} Successfully ${action}ed ${util.format.input(
+ target instanceof User ? target.tag : target.name
+ )}${global ? ' globally' : ''}.`,
+ allowedMentions: AllowedMentions.none()
+ });
}
}
diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts
index 6af5895..b88147d 100644
--- a/src/commands/config/config.ts
+++ b/src/commands/config/config.ts
@@ -97,7 +97,7 @@ export default class SettingsCommand extends BushCommand {
});
}
- override *args(message: BushMessage): Generator<ArgumentOptions | Flag> {
+ public override *args(message: BushMessage): Generator<ArgumentOptions | Flag> {
const optional = message.util.parsed!.alias === 'settings';
const setting = yield {
id: 'setting',
diff --git a/src/commands/config/disable.ts b/src/commands/config/disable.ts
index a30652a..44c28d3 100644
--- a/src/commands/config/disable.ts
+++ b/src/commands/config/disable.ts
@@ -1,6 +1,8 @@
-import { AllowedMentions, BushCommand, Global, type BushMessage, type BushSlashMessage } from '#lib';
+import { AllowedMentions, BushCommand, type BushMessage, type BushSlashMessage } from '#lib';
export default class DisableCommand extends BushCommand {
+ private static blacklistedCommands = ['eval', 'disable'];
+
public constructor() {
super('disable', {
aliases: ['disable', 'enable'],
@@ -45,20 +47,23 @@ export default class DisableCommand extends BushCommand {
});
}
- blacklistedCommands = ['eval', 'disable'];
-
public override async exec(
message: BushMessage | BushSlashMessage,
- args: { action: 'enable' | 'disable'; command: BushCommand | string; global: boolean }
+ args: { action?: 'enable' | 'disable'; command: BushCommand | string; global: boolean }
) {
let action = (args.action ?? message?.util?.parsed?.alias ?? 'toggle') as 'disable' | 'enable' | 'toggle';
const global = args.global && message.author.isOwner();
- const commandID = (args.command as BushCommand).id;
+ const commandID =
+ args.command instanceof BushCommand
+ ? args.command.id
+ : (await util.arg.cast(util.arg.union('commandAlias', 'command'), message, args.command))?.id;
+
+ if (!commandID) return await message.util.reply(`${util.emojis.error} Invalid command.`);
+
+ if (DisableCommand.blacklistedCommands.includes(commandID))
+ return message.util.send(`${util.emojis.error} the ${commandID} command cannot be disabled.`);
- const disabledCommands = global
- ? ((await Global.findByPk(client.config.environment)) ?? (await Global.create({ environment: client.config.environment })))
- .disabledCommands
- : await message.guild!.getSetting('disabledCommands');
+ const disabledCommands = global ? util.getGlobal('disabledCommands') : await message.guild!.getSetting('disabledCommands');
if (action === 'toggle') action = disabledCommands.includes(commandID) ? 'disable' : 'enable';
const newValue = util.addOrRemoveFromArray(action === 'disable' ? 'remove' : 'add', disabledCommands, commandID);
diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts
index 6121ad7..52cb8f5 100644
--- a/src/commands/config/log.ts
+++ b/src/commands/config/log.ts
@@ -22,11 +22,12 @@ export default class LogCommand extends BushCommand {
},
{
id: 'channel',
- description: 'The channel to have logs of the seleted type to be sent in.',
+ description: 'The channel to have logs of the selected type to be sent in.',
type: 'channel',
prompt: 'What channel would you like these logs to be sent in?',
slashType: 'CHANNEL',
- channelTypes: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD']
+ channelTypes: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'],
+ only: 'slash'
}
],
channel: 'guild',
@@ -35,7 +36,7 @@ export default class LogCommand extends BushCommand {
});
}
- override *args(): IterableIterator<ArgumentOptions | Flag> {
+ public override *args(): IterableIterator<ArgumentOptions | Flag> {
const log_type = yield {
id: 'log_type',
type: guildLogsArr,
diff --git a/src/commands/info/snowflake.ts b/src/commands/info/snowflake.ts
index bd0924f..f2ffaa8 100644
--- a/src/commands/info/snowflake.ts
+++ b/src/commands/info/snowflake.ts
@@ -4,7 +4,6 @@ import {
SnowflakeUtil,
VoiceChannel,
type CategoryChannel,
- type Channel,
type DeconstructedSnowflake,
type DMChannel,
type Guild,
@@ -46,9 +45,9 @@ export default class SnowflakeCommand extends BushCommand {
// Channel
if (client.channels.cache.has(snowflake)) {
- const channel: Channel = client.channels.cache.get(snowflake)!;
+ const channel = client.channels.cache.get(snowflake)!;
const channelInfo = [`**Type:** ${channel.type}`];
- if ((['DM', 'GROUP_DM'] as const).includes(channel.type)) {
+ if (['DM', 'GROUP_DM'].includes(channel.type)) {
const _channel = channel as DMChannel;
channelInfo.push(`**Recipient:** ${util.discord.escapeMarkdown(_channel.recipient.tag)} (${_channel.recipient.id})`);
snowflakeEmbed.setTitle(
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
index 2d7fcfb..c62be93 100644
--- a/src/commands/info/userInfo.ts
+++ b/src/commands/info/userInfo.ts
@@ -1,4 +1,11 @@
-import { BushCommand, type BushMessage, type BushSlashMessage, type BushUser } from '#lib';
+import {
+ BushCommand,
+ BushGuild,
+ BushGuildMember,
+ type BushMessage,
+ type BushSlashMessage,
+ type BushUser
+} from '#lib';
import { MessageEmbed, type Snowflake } from 'discord.js';
// TODO: Add bot information
@@ -37,11 +44,17 @@ export default class UserInfoCommand extends BushCommand {
: await client.users.fetch(`${args.user}`).catch(() => undefined);
if (user === undefined) return message.util.reply(`${util.emojis.error} Invalid user.`);
const member = message.guild ? message.guild.members.cache.get(user.id) : undefined;
+ await user.fetch(true); // gets banner info and accent color
+
+ const userEmbed = await UserInfoCommand.makeUserInfoEmbed(user, member, message.guild);
+
+ return await message.util.reply({ embeds: [userEmbed] });
+ }
+
+ public static async makeUserInfoEmbed(user: BushUser, member?: BushGuildMember, guild?: BushGuild | null) {
const emojis = [];
const superUsers = client.cache.global.superUsers;
- await user.fetch(true); // gets banner info and accent color
-
const userEmbed: MessageEmbed = new MessageEmbed()
.setTitle(util.discord.escapeMarkdown(user.tag))
.setThumbnail(user.displayAvatarURL({ size: 2048, format: 'png', dynamic: true }))
@@ -69,7 +82,7 @@ export default class UserInfoCommand extends BushCommand {
emojis.push(client.consts.mappings.otherEmojis.NITRO);
}
- if (message.guild?.ownerId == user.id) emojis.push(client.consts.mappings.otherEmojis.OWNER);
+ if (guild?.ownerId == user.id) emojis.push(client.consts.mappings.otherEmojis.OWNER);
else if (member?.permissions.has('ADMINISTRATOR')) emojis.push(client.consts.mappings.otherEmojis.ADMIN);
if (member?.premiumSinceTimestamp) emojis.push(client.consts.mappings.otherEmojis.BOOSTER);
@@ -92,16 +105,14 @@ export default class UserInfoCommand extends BushCommand {
// Server User Info
const serverUserInfo = [];
if (joinedAt)
- serverUserInfo.push(
- `**${message.guild!.ownerId == user.id ? 'Created Server' : 'Joined'}:** ${joinedAt} (${joinedAtDelta} ago)`
- );
+ serverUserInfo.push(`**${guild!.ownerId == user.id ? 'Created Server' : 'Joined'}:** ${joinedAt} (${joinedAtDelta} ago)`);
if (premiumSince) serverUserInfo.push(`**Boosting Since:** ${premiumSince} (${premiumSinceDelta} ago)`);
if (member?.displayHexColor) serverUserInfo.push(`**Display Color:** ${member.displayHexColor}`);
- if (user.id == '322862723090219008' && message.guild?.id == client.consts.mappings.guilds.bush)
+ if (user.id == '322862723090219008' && guild?.id == client.consts.mappings.guilds.bush)
serverUserInfo.push(`**General Deletions:** 1â…“`);
if (
(['384620942577369088', '496409778822709251'] as const).includes(user.id) &&
- message.guild?.id == client.consts.mappings.guilds.bush
+ guild?.id == client.consts.mappings.guilds.bush
)
serverUserInfo.push(`**General Deletions:** â…“`);
if (member?.nickname) serverUserInfo.push(`**Nickname:** ${util.discord.escapeMarkdown(member?.nickname)}`);
@@ -154,7 +165,7 @@ export default class UserInfoCommand extends BushCommand {
// Important Perms
const perms = [];
- if (member?.permissions.has('ADMINISTRATOR') || message.guild?.ownerId == user.id) {
+ if (member?.permissions.has('ADMINISTRATOR') || guild?.ownerId == user.id) {
perms.push('`Administrator`');
} else if (member?.permissions.toArray(false).length) {
member.permissions.toArray(false).forEach((permission) => {
@@ -166,7 +177,6 @@ export default class UserInfoCommand extends BushCommand {
if (perms.length) userEmbed.addField('» Important Perms', perms.join(' '));
if (emojis) userEmbed.setDescription(`\u200B${emojis.join(' ')}`); // zero width space
-
- return await message.util.reply({ embeds: [userEmbed] });
+ return userEmbed
}
}
diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts
index 275db38..689ef1e 100644
--- a/src/commands/moderation/role.ts
+++ b/src/commands/moderation/role.ts
@@ -56,7 +56,7 @@ export default class RoleCommand extends BushCommand {
});
}
- override *args(message: BushMessage): Generator<ArgumentOptions | Flag> {
+ public override *args(message: BushMessage): Generator<ArgumentOptions | Flag> {
const action = (['rr'] as const).includes(message.util.parsed?.alias ?? '')
? 'remove'
: (['ar', 'ra'] as const).includes(message.util.parsed?.alias ?? '')
diff --git a/src/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts
index 05f8d47..641f7ec 100644
--- a/src/commands/utilities/suicide.ts
+++ b/src/commands/utilities/suicide.ts
@@ -1,5 +1,5 @@
import { AllowedMentions, BushCommand, type BushMessage, type BushSlashMessage } from '#lib';
-import { MessageEmbed } from 'discord.js';
+import { Message, MessageEmbed } from 'discord.js';
export default class TemplateCommand extends BushCommand {
public constructor() {
@@ -46,8 +46,8 @@ export default class TemplateCommand extends BushCommand {
return (
// If the original message was a reply -> imitate it
- !message.util.isSlashMessage(message) && message.reference?.messageId && message.guild && message.channel
- ? await message.channel.messages.fetch(message.reference!.messageId!).then(async (message1) => {
+ !message.util.isSlashMessage(message) && (message as Message).reference?.messageId && message.guild && message.channel
+ ? await message.channel.messages.fetch((message as Message).reference!.messageId!).then(async (message1) => {
await message1.reply({
embeds: [suicideEmbed],
allowedMentions: AllowedMentions.users(),
diff --git a/src/context-menu-commands/user/userInfo.ts b/src/context-menu-commands/user/userInfo.ts
index 2ab265a..1e35093 100644
--- a/src/context-menu-commands/user/userInfo.ts
+++ b/src/context-menu-commands/user/userInfo.ts
@@ -1 +1,25 @@
-// todo: make context interaction for user command
+import { BushGuild, BushGuildMember, BushUser } from '#lib';
+import { ContextMenuCommand } from 'discord-akairo';
+import { type ContextMenuInteraction } from 'discord.js';
+import UserInfoCommand from '../../commands/info/userInfo.js';
+
+export default class UserInfoContextMenuCommand extends ContextMenuCommand {
+ public constructor() {
+ super('userInfo', {
+ name: 'User Info',
+ type: 'USER',
+ category: 'user'
+ });
+ }
+
+ public override async exec(interaction: ContextMenuInteraction) {
+ await interaction.deferReply({ ephemeral: true });
+
+ const user = (await interaction.user.fetch()) as BushUser;
+ const member = interaction.member as BushGuildMember;
+ const guild = interaction.guild as BushGuild;
+ const userEmbed = await UserInfoCommand.makeUserInfoEmbed(user, member, guild);
+
+ return await interaction.editReply({ embeds: [userEmbed] });
+ }
+}
diff --git a/src/inhibitors/blacklist/userGlobalBlacklist.ts b/src/inhibitors/blacklist/userGlobalBlacklist.ts
index 0f8cea7..ad906f8 100644
--- a/src/inhibitors/blacklist/userGlobalBlacklist.ts
+++ b/src/inhibitors/blacklist/userGlobalBlacklist.ts
@@ -12,8 +12,7 @@ export default class UserGlobalBlacklistInhibitor extends BushInhibitor {
public override exec(message: BushMessage | BushSlashMessage): boolean {
if (!message.author) return false;
- if (client.isOwner(message.author) || client.isSuperUser(message.author) || client.user!.id === message.author.id)
- return false;
+ if (client.isOwner(message.author) || client.user!.id === message.author.id) return false;
if (client.cache.global.blacklistedUsers.includes(message.author.id)) {
void client.console.verbose(
'userGlobalBlacklist',
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 932d457..e5487c3 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -261,6 +261,7 @@ export class AutoMod {
.addField('Message Content', `${await util.codeblock(this.message.content, 1024)}`)
.setColor(color)
.setTimestamp()
+ .setAuthor({name: this.message.author.tag, url: this.message.author.displayAvatarURL()})
],
components:
highestOffence.severity >= 2
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index 983eb56..d193b4d 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -1,4 +1,5 @@
import { DeleteButton, type BushMessage, type BushSlashMessage } from '#lib';
+import { CommandUtil } from 'discord-akairo';
import {
Constants,
MessageActionRow,
@@ -120,7 +121,7 @@ export class ButtonPaginator {
}
protected async end() {
- if (this.sentMessage && !this.sentMessage.deleted)
+ if (this.sentMessage && !CommandUtil.deletedMessages.has(this.sentMessage.id))
return await this.sentMessage
.edit({
content: this.text,
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index 38ce6df..e2509a9 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -1,4 +1,5 @@
import { PaginateEmojis, type BushMessage, type BushSlashMessage } from '#lib';
+import { CommandUtil } from 'discord-akairo';
import { Constants, MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
export class DeleteButton {
@@ -32,7 +33,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 (msg.deletable && !msg.deleted) await msg.delete();
+ if (msg.deletable && !CommandUtil.deletedMessages.has(msg.id)) await msg.delete();
}
});
diff --git a/src/lib/common/Moderation.ts b/src/lib/common/Moderation.ts
index a7a037f..ab2943b 100644
--- a/src/lib/common/Moderation.ts
+++ b/src/lib/common/Moderation.ts
@@ -10,13 +10,18 @@ import {
} from '#lib';
import { type Snowflake } from 'discord.js';
+/**
+ * A utility class with moderation-related methods.
+ */
export class Moderation {
/**
* Checks if a moderator can perform a moderation action on another user.
- * @param moderator - The person trying to perform the action.
- * @param victim - The person getting punished.
- * @param type - The type of punishment - used to format the response.
- * @param checkModerator - Whether or not to check if the victim is a moderator.
+ * @param moderator The person trying to perform the action.
+ * @param victim The person getting punished.
+ * @param type The type of punishment - used to format the response.
+ * @param checkModerator Whether or not to check if the victim is a moderator.
+ * @param force Override permissions checks.
+ * @returns `true` if the moderator can perform the action otherwise a reason why they can't.
*/
public static async permissionCheck(
moderator: BushGuildMember,
@@ -61,17 +66,14 @@ export class Moderation {
return true;
}
+ /**
+ * Creates a modlog entry for a punishment.
+ * @param options Options for creating a modlog entry.
+ * @param getCaseNumber Whether or not to get the case number of the entry.
+ * @returns An object with the modlog and the case number.
+ */
public static async createModLogEntry(
- options: {
- type: ModLogType;
- user: BushGuildMemberResolvable;
- moderator: BushGuildMemberResolvable;
- reason: string | undefined | null;
- duration?: number;
- guild: BushGuildResolvable;
- pseudo?: boolean;
- evidence?: string;
- },
+ options: CreateModLogEntryOptions,
getCaseNumber = false
): Promise<{ log: ModLog | null; caseNum: number | null }> {
const user = (await util.resolveNonCachedUser(options.user))!.id;
@@ -111,14 +113,12 @@ export class Moderation {
return { log: saveResult, caseNum };
}
- public static async createPunishmentEntry(options: {
- type: 'mute' | 'ban' | 'role' | 'block';
- user: BushGuildMemberResolvable;
- duration: number | undefined;
- guild: BushGuildResolvable;
- modlog: string;
- extraInfo?: Snowflake;
- }): Promise<ActivePunishment | null> {
+ /**
+ * Creates a punishment entry.
+ * @param options Options for creating the punishment entry.
+ * @returns The database entry, or null if no entry is created.
+ */
+ public static async createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> {
const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
const user = (await util.resolveNonCachedUser(options.user))!.id;
const guild = client.guilds.resolveId(options.guild)!;
@@ -135,12 +135,12 @@ export class Moderation {
});
}
- public static async removePunishmentEntry(options: {
- type: 'mute' | 'ban' | 'role' | 'block';
- user: BushGuildMemberResolvable;
- guild: BushGuildResolvable;
- extraInfo?: Snowflake;
- }): Promise<boolean> {
+ /**
+ * Destroys a punishment entry.
+ * @param options Options for destroying the punishment entry.
+ * @returns Whether or not the entry was destroyed.
+ */
+ public static async removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> {
const user = await util.resolveNonCachedUser(options.user);
const guild = client.guilds.resolveId(options.guild);
const type = this.findTypeEnum(options.type);
@@ -171,6 +171,11 @@ export class Moderation {
return success;
}
+ /**
+ * Returns the punishment type enum for the given type.
+ * @param type The type of the punishment.
+ * @returns The punishment type enum.
+ */
private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
const typeMap = {
['mute']: ActivePunishmentType.MUTE,
@@ -181,3 +186,108 @@ export class Moderation {
return typeMap[type];
}
}
+
+/**
+ * Options for creating a modlog entry.
+ */
+export interface CreateModLogEntryOptions {
+ /**
+ * The type of modlog entry.
+ */
+ type: ModLogType;
+
+ /**
+ * The user that a modlog entry is created for.
+ */
+ user: BushGuildMemberResolvable;
+
+ /**
+ * The moderator that created the modlog entry.
+ */
+ moderator: BushGuildMemberResolvable;
+
+ /**
+ * The reason for the punishment.
+ */
+ reason: string | undefined | null;
+
+ /**
+ * The duration of the punishment.
+ */
+ duration?: number;
+
+ /**
+ * The guild that the punishment is created for.
+ */
+ guild: BushGuildResolvable;
+
+ /**
+ * Whether the punishment is a pseudo punishment.
+ */
+ pseudo?: boolean;
+
+ /**
+ * The evidence for the punishment.
+ */
+ evidence?: string;
+}
+
+/**
+ * Options for creating a punishment entry.
+ */
+export interface CreatePunishmentEntryOptions {
+ /**
+ * The type of punishment.
+ */
+ type: 'mute' | 'ban' | 'role' | 'block';
+
+ /**
+ * The user that the punishment is created for.
+ */
+ user: BushGuildMemberResolvable;
+
+ /**
+ * The length of time the punishment lasts for.
+ */
+ duration: number | undefined;
+
+ /**
+ * The guild that the punishment is created for.
+ */
+ guild: BushGuildResolvable;
+
+ /**
+ * The id of the modlog that is linked to the punishment entry.
+ */
+ modlog: string;
+
+ /**
+ * The role id if the punishment is a role punishment.
+ */
+ extraInfo?: Snowflake;
+}
+
+/**
+ * Options for removing a punishment entry.
+ */
+export interface RemovePunishmentEntryOptions {
+ /**
+ * The type of punishment.
+ */
+ type: 'mute' | 'ban' | 'role' | 'block';
+
+ /**
+ * The user that the punishment is destroyed for.
+ */
+ user: BushGuildMemberResolvable;
+
+ /**
+ * The guild that the punishment was in.
+ */
+ guild: BushGuildResolvable;
+
+ /**
+ * The role id if the punishment is a role punishment.
+ */
+ extraInfo?: Snowflake;
+}
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts
index 9ce8b54..2577db9 100644
--- a/src/lib/common/util/Arg.ts
+++ b/src/lib/common/util/Arg.ts
@@ -1,7 +1,10 @@
import { BaseBushArgumentType, BushArgumentTypeCaster, BushSlashMessage, type BushArgumentType } from '#lib';
-import { Argument, type ArgumentTypeCaster, type Flag, type ParsedValuePredicate } from 'discord-akairo';
+import { Argument, type Flag, type ParsedValuePredicate } from 'discord-akairo';
import { type Message } from 'discord.js';
+/**
+ * A wrapper for the {@link Argument} class that adds custom typings.
+ */
export class Arg {
/**
* Casts a phrase to this argument's type.
@@ -11,14 +14,9 @@ export class Arg {
*/
public static async cast<T extends ATC>(type: T, message: Message | BushSlashMessage, phrase: string): Promise<ATCR<T>>;
public static async cast<T extends KBAT>(type: T, message: Message | BushSlashMessage, phrase: string): Promise<BAT[T]>;
- public static async cast<T extends AT | ATC>(type: T, message: Message | BushSlashMessage, phrase: string): Promise<any>;
+ public static async cast(type: AT | ATC, message: Message | BushSlashMessage, phrase: string): Promise<any>;
public static async cast(type: ATC | AT, message: Message | BushSlashMessage, phrase: string): Promise<any> {
- return Argument.cast(
- type as ArgumentTypeCaster | keyof BushArgumentType,
- client.commandHandler.resolver,
- message as Message,
- phrase
- );
+ return Argument.cast(type as any, client.commandHandler.resolver, message as Message, phrase);
}
/**
@@ -28,7 +26,7 @@ export class Arg {
*/
public static compose<T extends ATC>(...types: T[]): ATCATCR<T>;
public static compose<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static compose<T extends AT | ATC>(...types: T[]): ATC;
+ public static compose(...types: (AT | ATC)[]): ATC;
public static compose(...types: (AT | ATC)[]): ATC {
return Argument.compose(...(types as any));
}
@@ -40,7 +38,7 @@ export class Arg {
*/
public static composeWithFailure<T extends ATC>(...types: T[]): ATCATCR<T>;
public static composeWithFailure<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static composeWithFailure<T extends AT | ATC>(...types: T[]): ATC;
+ public static composeWithFailure(...types: (AT | ATC)[]): ATC;
public static composeWithFailure(...types: (AT | ATC)[]): ATC {
return Argument.composeWithFailure(...(types as any));
}
@@ -60,7 +58,7 @@ export class Arg {
*/
public static product<T extends ATC>(...types: T[]): ATCATCR<T>;
public static product<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static product<T extends AT | ATC>(...types: T[]): ATC;
+ public static product(...types: (AT | ATC)[]): ATC;
public static product(...types: (AT | ATC)[]): ATC {
return Argument.product(...(types as any));
}
@@ -74,7 +72,7 @@ export class Arg {
*/
public static range<T extends ATC>(type: T, min: number, max: number, inclusive?: boolean): ATCATCR<T>;
public static range<T extends KBAT>(type: T, min: number, max: number, inclusive?: boolean): ATCBAT<T>;
- public static range<T extends AT | ATC>(type: T, min: number, max: number, inclusive?: boolean): ATC;
+ public static range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC;
public static range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC {
return Argument.range(type as any, min, max, inclusive);
}
@@ -87,7 +85,7 @@ export class Arg {
*/
public static tagged<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
public static tagged<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
- public static tagged<T extends AT | ATC>(type: T, tag?: any): ATC;
+ public static tagged(type: AT | ATC, tag?: any): ATC;
public static tagged(type: AT | ATC, tag?: any): ATC {
return Argument.tagged(type as any, tag);
}
@@ -100,7 +98,7 @@ export class Arg {
*/
public static taggedUnion<T extends ATC>(...types: T[]): ATCATCR<T>;
public static taggedUnion<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static taggedUnion<T extends AT | ATC>(...types: T[]): ATC;
+ public static taggedUnion(...types: (AT | ATC)[]): ATC;
public static taggedUnion(...types: (AT | ATC)[]): ATC {
return Argument.taggedUnion(...(types as any));
}
@@ -113,7 +111,7 @@ export class Arg {
*/
public static taggedWithInput<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
public static taggedWithInput<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
- public static taggedWithInput<T extends AT | ATC>(type: T, tag?: any): ATC;
+ public static taggedWithInput(type: AT | ATC, tag?: any): ATC;
public static taggedWithInput(type: AT | ATC, tag?: any): ATC {
return Argument.taggedWithInput(type as any, tag);
}
@@ -125,7 +123,7 @@ export class Arg {
*/
public static union<T extends ATC>(...types: T[]): ATCATCR<T>;
public static union<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static union<T extends AT | ATC>(...types: T[]): ATC;
+ public static union(...types: (AT | ATC)[]): ATC;
public static union(...types: (AT | ATC)[]): ATC {
return Argument.union(...(types as any));
}
@@ -138,7 +136,7 @@ export class Arg {
*/
public static validate<T extends ATC>(type: T, predicate: ParsedValuePredicate): ATCATCR<T>;
public static validate<T extends KBAT>(type: T, predicate: ParsedValuePredicate): ATCBAT<T>;
- public static validate<T extends AT | ATC>(type: T, predicate: ParsedValuePredicate): ATC;
+ public static validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC;
public static validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC {
return Argument.validate(type as any, predicate);
}
@@ -150,39 +148,39 @@ export class Arg {
*/
public static withInput<T extends ATC>(type: T): ATC<ATCR<T>>;
public static withInput<T extends KBAT>(type: T): ATCBAT<T>;
- public static withInput<T extends AT | ATC>(type: T): ATC;
+ public static withInput(type: AT | ATC): ATC;
public static withInput(type: AT | ATC): ATC {
return Argument.withInput(type as any);
}
}
-type ArgumentTypeCasterReturn<R> = R extends BushArgumentTypeCaster<infer S> ? S : R;
+type BushArgumentTypeCasterReturn<R> = R extends BushArgumentTypeCaster<infer S> ? S : R;
/** ```ts
- * <R = unknown> = ArgumentTypeCaster<R>
+ * <R = unknown> = BushArgumentTypeCaster<R>
* ``` */
type ATC<R = unknown> = BushArgumentTypeCaster<R>;
/** ```ts
- * keyof BaseArgumentType
+ * keyof BaseBushArgumentType
* ``` */
type KBAT = keyof BaseBushArgumentType;
/** ```ts
- * <R> = ArgumentTypeCasterReturn<R>
+ * <R> = BushArgumentTypeCasterReturn<R>
* ``` */
-type ATCR<R> = ArgumentTypeCasterReturn<R>;
+type ATCR<R> = BushArgumentTypeCasterReturn<R>;
/** ```ts
- * keyof BaseBushArgumentType | string
+ * BushArgumentType
* ``` */
-type AT = BushArgumentTypeCaster | keyof BaseBushArgumentType | string;
+type AT = BushArgumentType;
/** ```ts
- * BaseArgumentType
+ * BaseBushArgumentType
* ``` */
type BAT = BaseBushArgumentType;
/** ```ts
- * <T extends ArgumentTypeCaster> = ArgumentTypeCaster<ArgumentTypeCasterReturn<T>>
+ * <T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>>
* ``` */
-type ATCATCR<T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<ArgumentTypeCasterReturn<T>>;
+type ATCATCR<T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>>;
/** ```ts
- * <T extends keyof BaseArgumentType> = ArgumentTypeCaster<BaseArgumentType[T]>
+ * <T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]>
* ``` */
type ATCBAT<T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]>;
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index a9e172a..d7c8b60 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -1,26 +1,25 @@
import type {
BushApplicationCommand,
BushBaseGuildEmojiManager,
- BushChannel,
BushChannelManager,
BushClientEvents,
BushClientUser,
BushGuildManager,
BushReactionEmoji,
+ BushStageChannel,
BushUserManager,
Config
} from '#lib';
-import { patch, type PatchedElements } from '@notenoughupdates/events-intercept';
+import { patch, PatchedElements } from '@notenoughupdates/events-intercept';
import * as Sentry from '@sentry/node';
import { AkairoClient, ContextMenuCommandHandler, version as akairoVersion } from 'discord-akairo';
import {
+ Awaitable,
Intents,
Options,
Structures,
version as discordJsVersion,
- type Awaitable,
type Collection,
- type DMChannel,
type InteractionReplyOptions,
type Message,
type MessageEditOptions,
@@ -31,6 +30,7 @@ import {
type Snowflake,
type WebhookEditMessageOptions
} from 'discord.js';
+import EventEmitter from 'events';
import path from 'path';
import readline from 'readline';
import type { Sequelize as SequelizeType } from 'sequelize';
@@ -100,9 +100,28 @@ export type BushEmojiIdentifierResolvable = string | BushEmojiResolvable;
export type BushThreadChannelResolvable = BushThreadChannel | Snowflake;
export type BushApplicationCommandResolvable = BushApplicationCommand | Snowflake;
export type BushGuildTextChannelResolvable = BushTextChannel | BushNewsChannel | Snowflake;
-export type BushChannelResolvable = BushChannel | Snowflake;
-export type BushTextBasedChannels = PartialDMChannel | BushDMChannel | BushTextChannel | BushNewsChannel | BushThreadChannel;
-export type BushGuildTextBasedChannel = Exclude<BushTextBasedChannels, PartialDMChannel | BushDMChannel | DMChannel>;
+export type BushChannelResolvable = BushAnyChannel | Snowflake;
+export type BushGuildChannelResolvable = Snowflake | BushGuildBasedChannel;
+export type BushAnyChannel =
+ | BushCategoryChannel
+ | BushDMChannel
+ | PartialDMChannel
+ | BushNewsChannel
+ | BushStageChannel
+ // eslint-disable-next-line deprecation/deprecation
+ | BushStoreChannel
+ | BushTextChannel
+ | BushThreadChannel
+ | BushVoiceChannel;
+export type BushTextBasedChannel = PartialDMChannel | BushThreadChannel | BushDMChannel | BushNewsChannel | BushTextChannel;
+export type BushTextBasedChannelTypes = BushTextBasedChannel['type'];
+export type BushVoiceBasedChannel = Extract<BushAnyChannel, { bitrate: number }>;
+export type BushGuildBasedChannel = Extract<BushAnyChannel, { guild: BushGuild }>;
+export type BushNonThreadGuildBasedChannel = Exclude<BushGuildBasedChannel, BushThreadChannel>;
+export type BushGuildTextBasedChannel = Extract<BushGuildBasedChannel, BushTextBasedChannel>;
+export type BushTextChannelResolvable = Snowflake | BushTextChannel;
+export type BushGuildVoiceChannelResolvable = BushVoiceBasedChannel | Snowflake;
+
export interface BushFetchedThreads {
threads: Collection<Snowflake, BushThreadChannel>;
hasMore?: boolean;
@@ -118,29 +137,86 @@ type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ?
const __dirname = path.dirname(fileURLToPath(import.meta.url));
+/**
+ * The main hub for interacting with the Discord API.
+ */
export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Ready> {
public declare channels: BushChannelManager;
public declare readonly emojis: BushBaseGuildEmojiManager;
public declare guilds: BushGuildManager;
public declare user: If<Ready, BushClientUser>;
public declare users: BushUserManager;
+ public declare util: BushClientUtil;
+ public declare ownerID: Snowflake[];
+ /**
+ * Whether or not the client is ready.
+ */
public customReady = false;
- public stats: { cpu: number | undefined; commandsUsed: bigint } = { cpu: undefined, commandsUsed: 0n };
+
+ /**
+ * Stats for the client.
+ */
+ public stats: BushStats = { cpu: undefined, commandsUsed: 0n };
+
+ /**
+ * The configuration for the client.
+ */
public config: Config;
+
+ /**
+ * The handler for the bot's listeners.
+ */
public listenerHandler: BushListenerHandler;
+
+ /**
+ * The handler for the bot's command inhibitors.
+ */
public inhibitorHandler: BushInhibitorHandler;
+
+ /**
+ * The handler for the bot's commands.
+ */
public commandHandler: BushCommandHandler;
+
+ /**
+ * The handler for the bot's tasks.
+ */
public taskHandler: BushTaskHandler;
+
+ /**
+ * The handler for the bot's context menu commands.
+ */
public contextMenuCommandHandler: ContextMenuCommandHandler;
- public declare util: BushClientUtil;
- public declare ownerID: Snowflake[];
+
+ /**
+ * The database connection for the bot.
+ */
public db: SequelizeType;
+
+ /**
+ * A custom logging system for the bot.
+ */
public logger = BushLogger;
+
+ /**
+ * Constants for the bot.
+ */
public constants = BushConstants;
+
+ /**
+ * Cached global and guild database data.
+ */
public cache = new BushCache();
+
+ /**
+ * Sentry error reporting for the bot.
+ */
public sentry!: typeof Sentry;
+ /**
+ * @param config The configuration for the bot.
+ */
public constructor(config: Config) {
super({
ownerID: config.owners,
@@ -163,25 +239,18 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
this.token = config.token as If<Ready, string, string | null>;
this.config = config;
- // Create listener handler
this.listenerHandler = new BushListenerHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'listeners'),
automateCategories: true
});
-
- // Create inhibitor handler
this.inhibitorHandler = new BushInhibitorHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'inhibitors'),
automateCategories: true
});
-
- // Create task handler
this.taskHandler = new BushTaskHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'tasks'),
automateCategories: true
});
-
- // Create command handler
this.commandHandler = new BushCommandHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'commands'),
prefix: async ({ guild }: Message) => {
@@ -215,12 +284,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
useSlashPermissions: true,
aliasReplacement: /-/g
});
-
this.contextMenuCommandHandler = new ContextMenuCommandHandler(this, {
directory: path.join(__dirname, '..', '..', '..', 'context-menu-commands'),
automateCategories: true
});
-
this.util = new BushClientUtil(this);
this.db = new Sequelize({
database: this.config.isDevelopment ? 'bushbot-dev' : this.config.isBeta ? 'bushbot-beta' : 'bushbot',
@@ -234,15 +301,24 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
});
}
- get console(): typeof BushLogger {
+ /**
+ * A custom logging system for the bot.
+ */
+ public get console(): typeof BushLogger {
return this.logger;
}
- get consts(): typeof BushConstants {
+ /**
+ * Constants for the bot.
+ */
+ public get consts(): typeof BushConstants {
return this.constants;
}
- public static init(): void {
+ /**
+ * Extends discord.js structures before the client is instantiated.
+ */
+ public static extendStructures(): void {
Structures.extend('GuildEmoji', () => BushGuildEmoji);
Structures.extend('DMChannel', () => BushDMChannel);
Structures.extend('TextChannel', () => BushTextChannel);
@@ -265,18 +341,28 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
Structures.extend('SelectMenuInteraction', () => BushSelectMenuInteraction);
}
- // Initialize everything
- async #init() {
- this.commandHandler.useListenerHandler(this.listenerHandler);
+ /**
+ * Initializes the bot.
+ */
+ async init() {
+ if (!process.version.startsWith('v17.')) {
+ void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false));
+ process.exit(2);
+ }
+
this.commandHandler.useInhibitorHandler(this.inhibitorHandler);
+ this.commandHandler.useListenerHandler(this.listenerHandler);
+ this.commandHandler.useTaskHandler(this.taskHandler);
+ this.commandHandler.useContextMenuCommandHandler(this.contextMenuCommandHandler);
this.commandHandler.ignorePermissions = this.config.owners;
this.commandHandler.ignoreCooldown = [...new Set([...this.config.owners, ...this.cache.global.superUsers])];
this.listenerHandler.setEmitters({
client: this,
commandHandler: this.commandHandler,
- listenerHandler: this.listenerHandler,
inhibitorHandler: this.inhibitorHandler,
+ listenerHandler: this.listenerHandler,
taskHandler: this.taskHandler,
+ contextMenuCommandHandler: this.contextMenuCommandHandler,
process,
stdin: rl,
gateway: this.ws
@@ -301,28 +387,31 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
void this.logger.success('startup', `Successfully connected to <<Sentry>>.`, false);
// loads all the handlers
- const loaders = {
+ const handlers = {
commands: this.commandHandler,
- contextMenuCommand: this.contextMenuCommandHandler,
+ contextMenuCommands: this.contextMenuCommandHandler,
listeners: this.listenerHandler,
inhibitors: this.inhibitorHandler,
tasks: this.taskHandler
};
- for (const loader in loaders) {
- try {
- await loaders[loader as keyof typeof loaders].loadAll();
- void this.logger.success('startup', `Successfully loaded <<${loader}>>.`, false);
- } catch (e) {
- void this.logger.error('startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack || e}`, false);
- }
- }
- await this.dbPreInit();
- await UpdateCacheTask.init(this);
- void this.console.success('startup', `Successfully created <<cache>>.`, false);
- this.stats.commandsUsed = await UpdateStatsTask.init();
+ const handlerPromises = Object.entries(handlers).map(([handlerName, handler]) =>
+ handler
+ .loadAll()
+ .then(() => {
+ void this.logger.success('startup', `Successfully loaded <<${handlerName}>>.`, false);
+ })
+ .catch((e) => {
+ void this.logger.error('startup', `Unable to load loader <<${handlerName}>> with error:\n${e?.stack || e}`, false);
+ if (process.argv.includes('dry')) process.exit(1);
+ })
+ );
+ await Promise.allSettled(handlerPromises);
}
- public async dbPreInit() {
+ /**
+ * Connects to the database, initializes models, and creates tables if they do not exist.
+ */
+ private async dbPreInit() {
try {
await this.db.authenticate();
Global.initModel(this.db);
@@ -348,10 +437,6 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
* Starts the bot
*/
public async start() {
- if (!process.version.startsWith('v17.')) {
- void (await this.console.error('version', `Please use node <<v17.x.x>>, not <<${process.version}>>.`, false));
- process.exit(2);
- }
this.intercept('ready', async (arg, done) => {
await this.guilds.fetch();
const promises = this.guilds.cache.map((guild) => {
@@ -368,7 +453,10 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
global.util = this.util;
try {
- await this.#init();
+ await this.dbPreInit();
+ await UpdateCacheTask.init(this);
+ void this.console.success('startup', `Successfully created <<cache>>.`, false);
+ this.stats.commandsUsed = await UpdateStatsTask.init();
await this.login(this.token!);
} catch (e) {
await this.console.error('start', util.inspect(e, { colors: true, depth: 1 }), false);
@@ -389,13 +477,14 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
public override isOwner(user: BushUserResolvable): boolean {
return this.config.owners.includes(this.users.resolveId(user!)!);
}
+
public override isSuperUser(user: BushUserResolvable): boolean {
const userID = this.users.resolveId(user)!;
return !!client.cache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID);
}
}
-export interface BushClient extends PatchedElements {
+export interface BushClient extends EventEmitter, PatchedElements {
on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this;
on<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this;
@@ -411,3 +500,15 @@ export interface BushClient extends PatchedElements {
removeAllListeners<K extends keyof BushClientEvents>(event?: K): this;
removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): this;
}
+
+export interface BushStats {
+ /**
+ * The average cpu usage of the bot from the past 60 seconds.
+ */
+ cpu: number | undefined;
+
+ /**
+ * The total number of times any command has been used.
+ */
+ commandsUsed: bigint;
+}
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index ab1f3ed..5ae2ac0 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -2,6 +2,7 @@ import {
Arg,
BushConstants,
Global,
+ GlobalCache,
type BushClient,
type BushInspectOptions,
type BushMessage,
@@ -438,6 +439,12 @@ export class BushClientUtil extends ClientUtil {
return array.join(', ');
}
+ public getGlobal(): GlobalCache;
+ public getGlobal<K extends keyof GlobalCache>(key: K): GlobalCache[K];
+ public getGlobal(key?: keyof GlobalCache) {
+ return key ? client.cache.global[key] : client.cache.global;
+ }
+
/**
* Add or remove an element from an array stored in the Globals database.
* @param action Either `add` or `remove` an element.
@@ -610,11 +617,11 @@ export class BushClientUtil extends ClientUtil {
/**
* Wait an amount in seconds.
- * @param s The number of seconds to wait
+ * @param seconds The number of seconds to wait
* @returns A promise that resolves after the specified amount of seconds
*/
- public async sleep(s: number) {
- return new Promise((resolve) => setTimeout(resolve, s * 1000));
+ public async sleep(seconds: number) {
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
/**
@@ -629,8 +636,13 @@ export class BushClientUtil extends ClientUtil {
});
}
+ /**
+ * Fetches a user from discord.
+ * @param user The user to fetch
+ * @returns Undefined if the user is not found, otherwise the user.
+ */
public async resolveNonCachedUser(user: UserResolvable | undefined | null): Promise<BushUser | undefined> {
- if (!user) return undefined;
+ if (user == null) return undefined;
const id =
user instanceof User || user instanceof GuildMember || user instanceof ThreadMember
? user.id
@@ -643,6 +655,11 @@ export class BushClientUtil extends ClientUtil {
else return await client.users.fetch(id).catch(() => undefined);
}
+ /**
+ * Get the pronouns of a discord user from pronoundb.org
+ * @param user The user to retrieve the promises of.
+ * @returns The human readable pronouns of the user, or undefined if they do not have any.
+ */
public async getPronounsOf(user: User | Snowflake): Promise<Pronoun | undefined> {
const _user = await this.resolveNonCachedUser(user);
if (!_user) throw new Error(`Cannot find user ${user}`);
@@ -657,6 +674,11 @@ export class BushClientUtil extends ClientUtil {
return client.constants.pronounMapping[apiRes.pronouns!]!;
}
+ /**
+ * List the methods of an object.
+ * @param obj The object to get the methods of.
+ * @returns A string with each method on a new line.
+ */
public getMethods(obj: Record<string, any>): string {
// modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class
// answer by Bruno Grieder
@@ -700,13 +722,17 @@ export class BushClientUtil extends ClientUtil {
return props.join('\n');
}
+ /**
+ * Uploads an image to imgur.
+ * @param image The image to upload.
+ * @returns The url of the imgur.
+ */
public async uploadImageToImgur(image: string) {
const clientId = this.client.config.credentials.imgurClientId;
const resp = (await got
.post('https://api.imgur.com/3/upload', {
headers: {
- // Authorization: `Bearer ${token}`,
Authorization: `Client-ID ${clientId}`,
Accept: 'application/json'
},
@@ -721,18 +747,38 @@ export class BushClientUtil extends ClientUtil {
return resp.data.link;
}
+ /**
+ * Checks if a user has a certain guild permission (doesn't check channel permissions).
+ * @param message The message to check the user from.
+ * @param permissions The permissions to check for.
+ * @returns The missing permissions or null if none are missing.
+ */
public userGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) {
const missing = message.member?.permissions.missing(permissions) ?? [];
return missing.length ? missing : null;
}
+ /**
+ * Check if the client has certain permissions in the guild (doesn't check channel permissions).
+ * @param message The message to check the client user from.
+ * @param permissions The permissions to check for.
+ * @returns The missing permissions or null if none are missing.
+ */
public clientGuildPermCheck(message: BushMessage | BushSlashMessage, permissions: PermissionResolvable) {
const missing = message.guild?.me?.permissions.missing(permissions) ?? [];
return missing.length ? missing : null;
}
+ /**
+ * Check if the client has permission to send messages in the channel as well as check if they have other permissions
+ * in the guild (or the channel if `checkChannel` is `true`).
+ * @param message The message to check the client user from.
+ * @param permissions The permissions to check for.
+ * @param checkChannel Whether to check the channel permissions instead of the guild permissions.
+ * @returns The missing permissions or null if none are missing.
+ */
public clientSendAndPermCheck(
message: BushMessage | BushSlashMessage,
permissions: PermissionResolvable = [],
@@ -752,6 +798,11 @@ export class BushClientUtil extends ClientUtil {
return missing.length ? missing : null;
}
+ /**
+ * Gets the prefix based off of the message.
+ * @param message The message to get the prefix from.
+ * @returns The prefix.
+ */
public prefix(message: BushMessage | BushSlashMessage): string {
return message.util.isSlash
? '/'
@@ -760,14 +811,55 @@ export class BushClientUtil extends ClientUtil {
: message.util.parsed?.prefix ?? client.config.prefix;
}
+ /**
+ * Recursively apply provided options operations on object
+ * and all of the object properties that are either object or function.
+ *
+ * By default freezes object.
+ *
+ * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions`
+ * @param options default `{ action: 'freeze' }`
+ * @param options.action
+ * ```
+ * | action | Add | Modify | Delete | Reconfigure |
+ * | ----------------- | --- | ------ | ------ | ----------- |
+ * | preventExtensions | - | + | + | + |
+ * | seal | - | + | - | - |
+ * | freeze | - | - | - | - |
+ * ```
+ *
+ * @returns Initial object with applied options action
+ */
public get deepFreeze() {
return deepLock;
}
+ /**
+ * Recursively apply provided options operations on object
+ * and all of the object properties that are either object or function.
+ *
+ * By default freezes object.
+ *
+ * @param obj - The object to which will be applied `freeze`, `seal` or `preventExtensions`
+ * @param options default `{ action: 'freeze' }`
+ * @param options.action
+ * ```
+ * | action | Add | Modify | Delete | Reconfigure |
+ * | ----------------- | --- | ------ | ------ | ----------- |
+ * | preventExtensions | - | + | + | + |
+ * | seal | - | + | - | - |
+ * | freeze | - | - | - | - |
+ * ```
+ *
+ * @returns Initial object with applied options action
+ */
public static get deepFreeze() {
return deepLock;
}
+ /**
+ * A wrapper for the Argument class that adds custom typings.
+ */
public get arg() {
return Arg;
}
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index ae3dcb2..6b54e20 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -174,7 +174,7 @@ export interface CustomBushArgumentOptions extends BaseBushArgumentOptions {
export type BushMissingPermissionSupplier = (message: BushMessage | BushSlashMessage) => Promise<any> | any;
-export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'> {
+interface ExtendedCommandOptions {
/**
* Whether the command is hidden from the help command.
*/
@@ -191,11 +191,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis
restrictedGuilds?: Snowflake[];
/**
- * The description of the command.
- */
- description: string;
-
- /**
* Show how to use the command.
*/
usage: string[];
@@ -206,13 +201,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis
examples: string[];
/**
- * The arguments for the command.
- */
- args?: BushArgumentOptions[] & CustomBushArgumentOptions[];
-
- category: string;
-
- /**
* A fake command, completely hidden from the help command.
*/
pseudo?: boolean;
@@ -223,6 +211,27 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis
bypassChannelBlacklist?: boolean;
/**
+ * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions
+ */
+ helpArgs?: BushArgumentOptions[];
+}
+
+export interface BaseBushCommandOptions
+ extends Omit<CommandOptions, 'userPermissions' | 'clientPermissions' | 'args'>,
+ ExtendedCommandOptions {
+ /**
+ * The description of the command.
+ */
+ description: string;
+
+ /**
+ * The arguments for the command.
+ */
+ args?: BushArgumentOptions[] & CustomBushArgumentOptions[];
+
+ category: string;
+
+ /**
* Permissions required by the client to run this command.
*/
clientPermissions: PermissionResolvable | PermissionResolvable[] | BushMissingPermissionSupplier;
@@ -241,11 +250,6 @@ export interface BaseBushCommandOptions extends Omit<CommandOptions, 'userPermis
* Restrict this argument to super users.
*/
superUserOnly?: boolean;
-
- /**
- * Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions
- */
- helpArgs?: BushArgumentOptions[];
}
export type BushCommandOptions = Omit<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>;
@@ -329,90 +333,67 @@ export class BushCommand extends Command {
});
}
- const newOptions: CommandOptions = {};
- if ('aliases' in options_) newOptions.aliases = options_.aliases;
- if ('args' in options_ && typeof options_.args === 'object') {
- const newTextArgs: ArgumentOptions[] = [];
- const newSlashArgs: SlashOption[] = [];
- for (const arg of options_.args) {
- if (arg.only !== 'slash' && !options_.slashOnly) {
- const newArg: ArgumentOptions = {};
- if ('default' in arg) newArg.default = arg.default;
- if ('description' in arg) newArg.description = arg.description;
- if ('flag' in arg) newArg.flag = arg.flag;
- if ('id' in arg) newArg.id = arg.id;
- if ('index' in arg) newArg.index = arg.index;
- if ('limit' in arg) newArg.limit = arg.limit;
- if ('match' in arg) newArg.match = arg.match;
- if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise;
- if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags;
- if ('otherwise' in arg) newArg.otherwise = arg.otherwise;
- if ('prompt' in arg || 'retry' in arg || 'optional' in arg) {
- newArg.prompt = {};
- if ('prompt' in arg) newArg.prompt.start = arg.prompt;
- if ('retry' in arg) newArg.prompt.retry = arg.retry;
- if ('optional' in arg) newArg.prompt.optional = arg.optional;
+ const newOptions: Partial<CommandOptions & ExtendedCommandOptions> = {};
+ for (const _key in options_) {
+ const key = _key as keyof typeof options_; // you got to love typescript
+ if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') {
+ const newTextArgs: ArgumentOptions[] = [];
+ const newSlashArgs: SlashOption[] = [];
+ for (const arg of options_.args) {
+ if (arg.only !== 'slash' && !options_.slashOnly) {
+ const newArg: ArgumentOptions = {};
+ if ('default' in arg) newArg.default = arg.default;
+ if ('description' in arg) newArg.description = arg.description;
+ if ('flag' in arg) newArg.flag = arg.flag;
+ if ('id' in arg) newArg.id = arg.id;
+ if ('index' in arg) newArg.index = arg.index;
+ if ('limit' in arg) newArg.limit = arg.limit;
+ if ('match' in arg) newArg.match = arg.match;
+ if ('modifyOtherwise' in arg) newArg.modifyOtherwise = arg.modifyOtherwise;
+ if ('multipleFlags' in arg) newArg.multipleFlags = arg.multipleFlags;
+ if ('otherwise' in arg) newArg.otherwise = arg.otherwise;
+ if ('prompt' in arg || 'retry' in arg || 'optional' in arg) {
+ newArg.prompt = {};
+ if ('prompt' in arg) newArg.prompt.start = arg.prompt;
+ if ('retry' in arg) newArg.prompt.retry = arg.retry;
+ if ('optional' in arg) newArg.prompt.optional = arg.optional;
+ }
+ if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster;
+ if ('unordered' in arg) newArg.unordered = arg.unordered;
+ newTextArgs.push(newArg);
+ }
+ if (
+ arg.only !== 'text' &&
+ !('slashOptions' in options_) &&
+ (options_.slash || options_.slashOnly) &&
+ arg.slashType !== false
+ ) {
+ const newArg: {
+ [key in SlashOptionKeys]?: any;
+ } = {
+ name: arg.id,
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ description: arg.prompt || arg.description || 'No description provided.',
+ type: arg.slashType
+ };
+ if ('slashResolve' in arg) newArg.resolve = arg.slashResolve;
+ if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete;
+ if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes;
+ if ('choices' in arg) newArg.choices = arg.choices;
+ if ('minValue' in arg) newArg.minValue = arg.minValue;
+ if ('maxValue' in arg) newArg.maxValue = arg.maxValue;
+ newArg.required = 'optional' in arg ? !arg.optional : true;
+ newSlashArgs.push(newArg as SlashOption);
}
- if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster;
- if ('unordered' in arg) newArg.unordered = arg.unordered;
- newTextArgs.push(newArg);
- }
- if (
- arg.only !== 'text' &&
- !('slashOptions' in options_) &&
- (options_.slash || options_.slashOnly) &&
- arg.slashType !== false
- ) {
- const newArg: {
- [key in SlashOptionKeys]?: any;
- } = {
- name: arg.id,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- description: arg.prompt || arg.description || 'No description provided.',
- type: arg.slashType
- };
- if ('slashResolve' in arg) newArg.resolve = arg.slashResolve;
- if ('autocomplete' in arg) newArg.autocomplete = arg.autocomplete;
- if ('channelTypes' in arg) newArg.channelTypes = arg.channelTypes;
- if ('choices' in arg) newArg.choices = arg.choices;
- if ('minValue' in arg) newArg.minValue = arg.minValue;
- if ('maxValue' in arg) newArg.maxValue = arg.maxValue;
- newArg.required = 'optional' in arg ? !arg.optional : true;
- newSlashArgs.push(newArg as SlashOption);
}
+ if (newTextArgs.length > 0) newOptions.args = newTextArgs;
+ if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs;
+ } else if (key === 'clientPermissions' || key === 'userPermissions') {
+ newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier;
+ } else {
+ newOptions[key] = options_[key];
}
- if (newTextArgs.length > 0) newOptions.args = newTextArgs;
- if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs;
}
- type perm = PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier;
-
- if ('argumentDefaults' in options_) newOptions.argumentDefaults = options_.argumentDefaults;
- if ('before' in options_) newOptions.before = options_.before;
- if ('channel' in options_) newOptions.channel = options_.channel;
- if ('clientPermissions' in options_) newOptions.clientPermissions = options_.clientPermissions as perm;
- if ('condition' in options_) newOptions.condition = options_.condition;
- if ('cooldown' in options_) newOptions.cooldown = options_.cooldown;
- if ('description' in options_) newOptions.description = options_.description;
- if ('editable' in options_) newOptions.editable = options_.editable;
- if ('flags' in options_) newOptions.flags = options_.flags;
- if ('ignoreCooldown' in options_) newOptions.ignoreCooldown = options_.ignoreCooldown;
- if ('ignorePermissions' in options_) newOptions.ignorePermissions = options_.ignorePermissions;
- if ('lock' in options_) newOptions.lock = options_.lock;
- if ('onlyNsfw' in options_) newOptions.onlyNsfw = options_.onlyNsfw;
- if ('optionFlags' in options_) newOptions.optionFlags = options_.optionFlags;
- if ('ownerOnly' in options_) newOptions.ownerOnly = options_.ownerOnly;
- if ('prefix' in options_) newOptions.prefix = options_.prefix;
- if ('quoted' in options_) newOptions.quoted = options_.quoted;
- if ('ratelimit' in options_) newOptions.ratelimit = options_.ratelimit;
- if ('regex' in options_) newOptions.regex = options_.regex;
- if ('separator' in options_) newOptions.separator = options_.separator;
- if ('slash' in options_) newOptions.slash = options_.slash;
- if ('slashEphemeral' in options_) newOptions.slashEphemeral = options_.slashEphemeral;
- if ('slashGuilds' in options_) newOptions.slashGuilds = options_.slashGuilds;
- if ('slashOptions' in options_) newOptions.slashOptions = options_.slashOptions;
- if ('superUserOnly' in options_) newOptions.superUserOnly = options_.superUserOnly;
- if ('typing' in options_) newOptions.typing = options_.typing;
- if ('userPermissions' in options_) newOptions.userPermissions = options_.userPermissions as perm;
super(id, newOptions);
@@ -447,9 +428,14 @@ export class BushCommand extends Command {
}
}
-export interface BushCommand {
- exec(message: BushMessage, args: any): any;
- exec(message: BushMessage | BushSlashMessage, args: any): any;
+export interface BushCommand extends Command {
+ /**
+ * Executes the command.
+ * @param message - Message that triggered the command.
+ * @param args - Evaluated arguments.
+ */
+ exec<R, A>(message: BushMessage, args: A): R;
+ exec<R, A>(message: BushMessage | BushSlashMessage, args: A): R;
}
type SlashOptionKeys =
diff --git a/src/lib/extensions/discord-akairo/BushInhibitor.ts b/src/lib/extensions/discord-akairo/BushInhibitor.ts
index c3f9994..7f13594 100644
--- a/src/lib/extensions/discord-akairo/BushInhibitor.ts
+++ b/src/lib/extensions/discord-akairo/BushInhibitor.ts
@@ -6,6 +6,13 @@ export class BushInhibitor extends Inhibitor {
}
export interface BushInhibitor {
+ /**
+ * Checks if message should be blocked.
+ * A return value of true will block the message.
+ * If returning a Promise, a resolved value of true will block the message.
+ * @param message - Message being handled.
+ * @param command - Command to check.
+ */
exec(message: BushMessage, command: BushCommand): any;
exec(message: BushMessage | BushSlashMessage, command: BushCommand): any;
}
diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts
index c0a4a60..448963b 100644
--- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts
+++ b/src/lib/extensions/discord-akairo/BushSlashMessage.ts
@@ -12,6 +12,6 @@ export class BushSlashMessage extends AkairoMessage {
}
}
-export interface BushSlashMessage {
+export interface BushSlashMessage extends AkairoMessage {
get guild(): BushGuild | null;
}
diff --git a/src/lib/extensions/discord.js/BushActivity.ts b/src/lib/extensions/discord.js/BushActivity.ts
index e438918..3f19232 100644
--- a/src/lib/extensions/discord.js/BushActivity.ts
+++ b/src/lib/extensions/discord.js/BushActivity.ts
@@ -2,6 +2,9 @@ import type { BushEmoji, BushPresence } from '#lib';
import { Activity } from 'discord.js';
import type { RawActivityData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents an activity that is part of a user's presence.
+ */
export class BushActivity extends Activity {
public declare emoji: BushEmoji | null;
diff --git a/src/lib/extensions/discord.js/BushApplicationCommand.ts b/src/lib/extensions/discord.js/BushApplicationCommand.ts
index 35d598d..8298830 100644
--- a/src/lib/extensions/discord.js/BushApplicationCommand.ts
+++ b/src/lib/extensions/discord.js/BushApplicationCommand.ts
@@ -3,6 +3,9 @@ import type { BushClient, BushGuild } from '#lib';
import { ApplicationCommand, type Snowflake } from 'discord.js';
import type { RawApplicationCommandData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents an application command.
+ */
export class BushApplicationCommand<PermissionsFetchType = {}> extends ApplicationCommand<PermissionsFetchType> {
public declare readonly client: BushClient;
public declare guild: BushGuild | null;
diff --git a/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts b/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts
index 7947acd..2aa366d 100644
--- a/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts
+++ b/src/lib/extensions/discord.js/BushApplicationCommandManager.d.ts
@@ -14,12 +14,19 @@ import {
type Snowflake
} from 'discord.js';
+/**
+ * Manages API methods for application commands and stores their cache.
+ */
export class BushApplicationCommandManager<
ApplicationCommandScope = BushApplicationCommand<{ guild: BushGuildResolvable }>,
PermissionsOptionsExtras = { guild: BushGuildResolvable },
PermissionsGuildType = null
> extends CachedManager<Snowflake, ApplicationCommandScope, BushApplicationCommandResolvable> {
public constructor(client: BushClient, iterable?: Iterable<unknown>);
+
+ /**
+ * The manager for permissions of arbitrary commands on arbitrary guilds
+ */
public permissions: BushApplicationCommandPermissionsManager<
{ command?: BushApplicationCommandResolvable } & PermissionsOptionsExtras,
{ command: BushApplicationCommandResolvable } & PermissionsOptionsExtras,
@@ -27,22 +34,112 @@ export class BushApplicationCommandManager<
PermissionsGuildType,
null
>;
+
+ /**
+ * The APIRouter path to the commands
+ * @param id The application command's id
+ * @param guildId The guild's id to use in the path,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ */
private commandPath({ id, guildId }: { id?: Snowflake; guildId?: Snowflake }): unknown;
- public create(command: ApplicationCommandData): Promise<ApplicationCommandScope>;
- public create(command: ApplicationCommandData, guildId: Snowflake): Promise<BushApplicationCommand>;
+
+ /**
+ * Creates an application command.
+ * @param command The command
+ * @param guildId The guild's id to create this command in, ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Create a new command
+ * client.application.commands.create({
+ * name: 'test',
+ * description: 'A test command',
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ public create(command: BushApplicationCommandResolvable, guildId?: Snowflake): Promise<ApplicationCommandScope>;
+
+ /**
+ * Deletes an application command.
+ * @param command The command to delete
+ * @param guildId The guild's id where the command is registered,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Delete a command
+ * guild.commands.delete('123456789012345678')
+ * .then(console.log)
+ * .catch(console.error);
+ */
public delete(command: BushApplicationCommandResolvable, guildId?: Snowflake): Promise<ApplicationCommandScope | null>;
+
+ /**
+ * Edits an application command.
+ * @param command The command to edit
+ * @param data The data to update the command with
+ * @param guildId The guild's id where the command registered,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Edit an existing command
+ * client.application.commands.edit('123456789012345678', {
+ * description: 'New description',
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
public edit(command: BushApplicationCommandResolvable, data: ApplicationCommandData): Promise<ApplicationCommandScope>;
public edit(
command: BushApplicationCommandResolvable,
data: ApplicationCommandData,
guildId: Snowflake
): Promise<BushApplicationCommand>;
+
+ /**
+ * Obtains one or multiple application commands from Discord, or the cache if it's already available.
+ * @param id The application command's id
+ * @param options Additional options for this fetch
+ * @example
+ * // Fetch a single command
+ * client.application.commands.fetch('123456789012345678')
+ * .then(command => console.log(`Fetched command ${command.name}`))
+ * .catch(console.error);
+ * @example
+ * // Fetch all commands
+ * guild.commands.fetch()
+ * .then(commands => console.log(`Fetched ${commands.size} commands`))
+ * .catch(console.error);
+ */
public fetch(id: Snowflake, options: FetchApplicationCommandOptions & { guildId: Snowflake }): Promise<BushApplicationCommand>;
public fetch(options: FetchApplicationCommandOptions): Promise<Collection<string, ApplicationCommandScope>>;
public fetch(id: Snowflake, options?: FetchApplicationCommandOptions): Promise<ApplicationCommandScope>;
public fetch(id?: Snowflake, options?: FetchApplicationCommandOptions): Promise<Collection<Snowflake, ApplicationCommandScope>>;
+
+ /**
+ * Sets all the commands for this application or guild.
+ * @param commands The commands
+ * @param guildId The guild's id to create the commands in,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Set all commands to just this one
+ * client.application.commands.set([
+ * {
+ * name: 'test',
+ * description: 'A test command',
+ * },
+ * ])
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Remove all commands
+ * guild.commands.set([])
+ * .then(console.log)
+ * .catch(console.error);
+ */
public set(commands: ApplicationCommandData[]): Promise<Collection<Snowflake, ApplicationCommandScope>>;
public set(commands: ApplicationCommandData[], guildId: Snowflake): Promise<Collection<Snowflake, BushApplicationCommand>>;
+
+ /**
+ * Transforms an {@link ApplicationCommandData} object into something that can be used with the API.
+ * @param command The command to transform
+ */
private static transformCommand(
command: ApplicationCommandData
): Omit<APIApplicationCommand, 'id' | 'application_id' | 'guild_id'>;
diff --git a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts b/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts
index 5f2e4da..ff32be4 100644
--- a/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts
+++ b/src/lib/extensions/discord.js/BushApplicationCommandPermissionsManager.d.ts
@@ -13,6 +13,9 @@ import {
} from 'discord.js';
import type { ApplicationCommandPermissionTypes } from 'discord.js/typings/enums';
+/**
+ * Manages API methods for permissions of Application Commands.
+ */
export class BushApplicationCommandPermissionsManager<
BaseOptions,
FetchSingleOptions,
@@ -21,18 +24,95 @@ export class BushApplicationCommandPermissionsManager<
CommandIdType
> extends BaseManager {
public constructor(manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand);
+
+ /**
+ * The manager or command that this manager belongs to
+ */
private manager: ApplicationCommandManager | GuildApplicationCommandManager | ApplicationCommand;
+ /**
+ * The client that instantiated this Manager
+ */
public client: BushClient;
+
+ /**
+ * The id of the command this manager acts on
+ */
public commandId: CommandIdType;
+
+ /**
+ * The guild that this manager acts on
+ */
public guild: GuildType;
+
+ /**
+ * The id of the guild that this manager acts on
+ */
public guildId: Snowflake | null;
+
+ /**
+ * Add permissions to a command.
+ * @param options Options used to add permissions
+ * @example
+ * // Block a role from the command permissions
+ * guild.commands.permissions.add({ command: '123456789012345678', permissions: [
+ * {
+ * id: '876543211234567890',
+ * type: 'ROLE',
+ * permission: false
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ */
public add(
options: FetchSingleOptions & { permissions: ApplicationCommandPermissionData[] }
): Promise<ApplicationCommandPermissions[]>;
+
+ /**
+ * Check whether a permission exists for a user or role
+ * @param options Options used to check permissions
+ * @example
+ * // Check whether a user has permission to use a command
+ * guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' })
+ * .then(console.log)
+ * .catch(console.error);
+ */
public has(options: FetchSingleOptions & { permissionId: BushUserResolvable | BushRoleResolvable }): Promise<boolean>;
+
+ /**
+ * Fetches the permissions for one or multiple commands.
+ * @param options Options used to fetch permissions
+ * @example
+ * // Fetch permissions for one command
+ * guild.commands.permissions.fetch({ command: '123456789012345678' })
+ * .then(perms => console.log(`Fetched permissions for ${perms.length} users`))
+ * .catch(console.error);
+ * @example
+ * // Fetch permissions for all commands in a guild
+ * client.application.commands.permissions.fetch({ guild: '123456789012345678' })
+ * .then(perms => console.log(`Fetched permissions for ${perms.size} commands`))
+ * .catch(console.error);
+ */
public fetch(options: FetchSingleOptions): Promise<ApplicationCommandPermissions[]>;
public fetch(options: BaseOptions): Promise<Collection<Snowflake, ApplicationCommandPermissions[]>>;
+
+ /**
+ * Remove permissions from a command.
+ * @param options Options used to remove permissions
+ * @example
+ * // Remove a user permission from this command
+ * guild.commands.permissions.remove({ command: '123456789012345678', users: '876543210123456789' })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Remove multiple roles from this command
+ * guild.commands.permissions.remove({
+ * command: '123456789012345678', roles: ['876543210123456789', '765432101234567890']
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
public remove(
options:
| (FetchSingleOptions & {
@@ -44,6 +124,37 @@ export class BushApplicationCommandPermissionsManager<
roles: BushRoleResolvable | BushRoleResolvable[];
})
): Promise<ApplicationCommandPermissions[]>;
+
+ /**
+ * Sets the permissions for one or more commands.
+ * @param options Options used to set permissions
+ * @example
+ * // Set the permissions for one command
+ * client.application.commands.permissions.set({ guild: '892455839386304532', command: '123456789012345678',
+ * permissions: [
+ * {
+ * id: '876543210987654321',
+ * type: 'USER',
+ * permission: false,
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Set the permissions for all commands
+ * guild.commands.permissions.set({ fullPermissions: [
+ * {
+ * id: '123456789012345678',
+ * permissions: [{
+ * id: '876543210987654321',
+ * type: 'USER',
+ * permission: false,
+ * }],
+ * },
+ * ]})
+ * .then(console.log)
+ * .catch(console.error);
+ */
public set(
options: FetchSingleOptions & { permissions: ApplicationCommandPermissionData[] }
): Promise<ApplicationCommandPermissions[]>;
@@ -52,7 +163,19 @@ export class BushApplicationCommandPermissionsManager<
fullPermissions: GuildApplicationCommandPermissionData[];
}
): Promise<Collection<Snowflake, ApplicationCommandPermissions[]>>;
+
+ /**
+ * The APIRouter path to the commands
+ * @param guildId The guild's id to use in the path,
+ * @param commandId The application command's id
+ */
private permissionsPath(guildId: Snowflake, commandId?: Snowflake): unknown;
+
+ /**
+ * Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
+ * @param permissions The permissions to transform
+ * @param received Whether these permissions have been received from Discord
+ */
private static transformPermissions(
permissions: ApplicationCommandPermissionData,
received: true
diff --git a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts b/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts
index 7c913a9..347ff65 100644
--- a/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts
+++ b/src/lib/extensions/discord.js/BushBaseGuildEmojiManager.d.ts
@@ -2,7 +2,15 @@ import type { BushClient, BushEmojiIdentifierResolvable, BushEmojiResolvable, Bu
import { CachedManager, type Snowflake } from 'discord.js';
import { type RawGuildEmojiData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Holds methods to resolve GuildEmojis and stores their cache.
+ */
export class BushBaseGuildEmojiManager extends CachedManager<Snowflake, BushGuildEmoji, BushEmojiResolvable> {
public constructor(client: BushClient, iterable?: Iterable<RawGuildEmojiData>);
+
+ /**
+ * Resolves an EmojiResolvable to an emoji identifier.
+ * @param emoji The emoji resolvable to resolve
+ */
public resolveIdentifier(emoji: BushEmojiIdentifierResolvable): string | null;
}
diff --git a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts
index 742ea76..9b260dc 100644
--- a/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts
+++ b/src/lib/extensions/discord.js/BushBaseGuildTextChannel.ts
@@ -8,6 +8,9 @@ import {
} from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a text-based guild channel on Discord.
+ */
export class BushBaseGuildTextChannel extends BaseGuildTextChannel {
public declare messages: BushMessageManager;
public declare threads: BushThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
diff --git a/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts b/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts
new file mode 100644
index 0000000..21be206
--- /dev/null
+++ b/src/lib/extensions/discord.js/BushBaseGuildVoiceChannel.d.ts
@@ -0,0 +1,13 @@
+import { BaseGuildVoiceChannel, Collection, Snowflake } from 'discord.js';
+import { BushCategoryChannel } from './BushCategoryChannel';
+import { BushGuild } from './BushGuild';
+import { BushGuildMember } from './BushGuildMember';
+
+/**
+ * Represents a voice-based guild channel on Discord.
+ */
+export class BushBaseGuildVoiceChannel extends BaseGuildVoiceChannel {
+ public readonly members: Collection<Snowflake, BushGuildMember>;
+ public guild: BushGuild;
+ public readonly parent: BushCategoryChannel | null;
+}
diff --git a/src/lib/extensions/discord.js/BushButtonInteraction.ts b/src/lib/extensions/discord.js/BushButtonInteraction.ts
index 18d6b35..2bc1c25 100644
--- a/src/lib/extensions/discord.js/BushButtonInteraction.ts
+++ b/src/lib/extensions/discord.js/BushButtonInteraction.ts
@@ -1,15 +1,18 @@
-import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannels, BushUser } from '#lib';
+import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib';
import type { APIInteractionGuildMember } from 'discord-api-types/v9';
import { ButtonInteraction, type CacheType, type CacheTypeReducer } from 'discord.js';
import type { RawMessageButtonInteractionData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a button interaction.
+ */
export class BushButtonInteraction<Cached extends CacheType = CacheType> extends ButtonInteraction<Cached> {
public declare readonly channel: CacheTypeReducer<
Cached,
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
- BushTextBasedChannels | null
+ BushTextBasedChannel | null
>;
public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>;
public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>;
diff --git a/src/lib/extensions/discord.js/BushCategoryChannel.ts b/src/lib/extensions/discord.js/BushCategoryChannel.ts
index c30a761..b711a54 100644
--- a/src/lib/extensions/discord.js/BushCategoryChannel.ts
+++ b/src/lib/extensions/discord.js/BushCategoryChannel.ts
@@ -1,10 +1,13 @@
-import { type BushClient, type BushGuild, type BushGuildChannel, type BushGuildMember } from '#lib';
+import { BushNonThreadGuildBasedChannel, type BushClient, type BushGuild, type BushGuildMember } from '#lib';
import { CategoryChannel, type Collection, type Snowflake } from 'discord.js';
import { type RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild category channel on Discord.
+ */
export class BushCategoryChannel extends CategoryChannel {
public declare readonly client: BushClient;
- public declare readonly children: Collection<Snowflake, BushGuildChannel>;
+ public declare readonly children: Collection<Snowflake, Exclude<BushNonThreadGuildBasedChannel, BushCategoryChannel>>;
public declare guild: BushGuild;
public declare readonly members: Collection<Snowflake, BushGuildMember>;
public declare readonly parent: CategoryChannel | null;
diff --git a/src/lib/extensions/discord.js/BushChannel.d.ts b/src/lib/extensions/discord.js/BushChannel.d.ts
index 9a78b9a..42443ba 100644
--- a/src/lib/extensions/discord.js/BushChannel.d.ts
+++ b/src/lib/extensions/discord.js/BushChannel.d.ts
@@ -1,8 +1,12 @@
-import type { BushClient, BushStageChannel, BushTextBasedChannels, BushThreadChannel, BushVoiceChannel } from '#lib';
+import type { BushClient, BushTextBasedChannel, BushThreadChannel } from '#lib';
import { Channel, type ChannelMention, type Snowflake } from 'discord.js';
import type { ChannelTypes } from 'discord.js/typings/enums';
import type { RawChannelData } from 'discord.js/typings/rawDataTypes';
+import { BushBaseGuildVoiceChannel } from './BushBaseGuildVoiceChannel';
+/**
+ * Represents any channel on Discord.
+ */
export class BushChannel extends Channel {
public constructor(client: BushClient, data?: RawChannelData, immediatePatch?: boolean);
public readonly createdAt: Date;
@@ -11,10 +15,10 @@ export class BushChannel extends Channel {
public id: Snowflake;
public readonly partial: false;
public type: keyof typeof ChannelTypes;
- public delete(): Promise<BushChannel>;
- public fetch(force?: boolean): Promise<BushChannel>;
- public isText(): this is BushTextBasedChannels;
- public isVoice(): this is BushVoiceChannel | BushStageChannel;
+ public delete(): Promise<this>;
+ public fetch(force?: boolean): Promise<this>;
+ public isText(): this is BushTextBasedChannel;
+ public isVoice(): this is BushBaseGuildVoiceChannel;
public isThread(): this is BushThreadChannel;
public toString(): ChannelMention;
}
diff --git a/src/lib/extensions/discord.js/BushChannelManager.d.ts b/src/lib/extensions/discord.js/BushChannelManager.d.ts
index 843b956..514cdd3 100644
--- a/src/lib/extensions/discord.js/BushChannelManager.d.ts
+++ b/src/lib/extensions/discord.js/BushChannelManager.d.ts
@@ -1,8 +1,22 @@
-import type { BushChannel, BushChannelResolvable } from '#lib';
+import type { BushAnyChannel, BushChannelResolvable } from '#lib';
import { CachedManager, type Client, type FetchChannelOptions, type Snowflake } from 'discord.js';
import type { RawChannelData } from 'discord.js/typings/rawDataTypes';
-export class BushChannelManager extends CachedManager<Snowflake, BushChannel, BushChannelResolvable> {
+/**
+ * A manager of channels belonging to a client
+ */
+export class BushChannelManager extends CachedManager<Snowflake, BushAnyChannel, BushChannelResolvable> {
public constructor(client: Client, iterable: Iterable<RawChannelData>);
- public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<BushChannel | null>;
+
+ /**
+ * Obtains a channel from Discord, or the channel cache if it's already available.
+ * @param id The channel's id
+ * @param options Additional options for this fetch
+ * @example
+ * // Fetch a channel by its id
+ * client.channels.fetch('222109930545610754')
+ * .then(channel => console.log(channel.name))
+ * .catch(console.error);
+ */
+ public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<BushAnyChannel | null>;
}
diff --git a/src/lib/extensions/discord.js/BushClientEvents.d.ts b/src/lib/extensions/discord.js/BushClientEvents.d.ts
index 91096bd..10d70f9 100644
--- a/src/lib/extensions/discord.js/BushClientEvents.d.ts
+++ b/src/lib/extensions/discord.js/BushClientEvents.d.ts
@@ -4,16 +4,16 @@ import type {
BushDMChannel,
BushGuild,
BushGuildBan,
- BushGuildChannel,
BushGuildEmoji,
BushGuildMember,
BushMessage,
BushMessageReaction,
BushNewsChannel,
+ BushNonThreadGuildBasedChannel,
BushPresence,
BushRole,
BushStageInstance,
- BushTextBasedChannels,
+ BushTextBasedChannel,
BushTextChannel,
BushThreadChannel,
BushThreadMember,
@@ -29,6 +29,7 @@ import type {
import type { AkairoClientEvents } from 'discord-akairo';
import type {
Collection,
+ GuildScheduledEvent,
Interaction,
InvalidRequestWarningData,
Invite,
@@ -45,12 +46,12 @@ export interface BushClientEvents extends AkairoClientEvents {
oldCommand: BushApplicationCommand | null,
newCommand: BushApplicationCommand
];
- channelCreate: [channel: BushGuildChannel];
- channelDelete: [channel: BushDMChannel | BushGuildChannel];
- channelPinsUpdate: [channel: BushTextBasedChannels, date: Date];
+ channelCreate: [channel: BushNonThreadGuildBasedChannel];
+ channelDelete: [channel: BushDMChannel | BushNonThreadGuildBasedChannel];
+ channelPinsUpdate: [channel: BushTextBasedChannel, date: Date];
channelUpdate: [
- oldChannel: BushDMChannel | BushGuildChannel,
- newChannel: BushDMChannel | BushGuildChannel
+ oldChannel: BushDMChannel | BushNonThreadGuildBasedChannel,
+ newChannel: BushDMChannel | BushNonThreadGuildBasedChannel
];
debug: [message: string];
warn: [message: string];
@@ -145,6 +146,20 @@ export interface BushClientEvents extends AkairoClientEvents {
stickerCreate: [sticker: Sticker];
stickerDelete: [sticker: Sticker];
stickerUpdate: [oldSticker: Sticker, newSticker: Sticker];
+ guildScheduledEventCreate: [guildScheduledEvent: GuildScheduledEvent];
+ guildScheduledEventUpdate: [
+ oldGuildScheduledEvent: GuildScheduledEvent,
+ newGuildScheduledEvent: GuildScheduledEvent
+ ];
+ guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent];
+ guildScheduledEventUserAdd: [
+ guildScheduledEvent: GuildScheduledEvent,
+ user: BushUser
+ ];
+ guildScheduledEventUserRemove: [
+ guildScheduledEvent: GuildScheduledEvent,
+ user: BushUser
+ ];
/* Custom */
bushBan: [
victim: BushGuildMember | BushUser,
diff --git a/src/lib/extensions/discord.js/BushClientUser.d.ts b/src/lib/extensions/discord.js/BushClientUser.d.ts
index c307553..503413b 100644
--- a/src/lib/extensions/discord.js/BushClientUser.d.ts
+++ b/src/lib/extensions/discord.js/BushClientUser.d.ts
@@ -3,22 +3,96 @@ import type {
Base64Resolvable,
BufferResolvable,
ClientPresence,
+ ClientUser,
ClientUserEditData,
PresenceData,
PresenceStatusData
} from 'discord.js';
import { BushUser } from './BushUser';
-export class BushClientUser extends BushUser {
+/**
+ * Represents the logged in client's Discord user.
+ */
+export class BushClientUser extends BushUser implements ClientUser {
+ /**
+ * If the bot's {@link ClientApplication.owner Owner} has MFA enabled on their account
+ */
public mfaEnabled: boolean;
+
+ /**
+ * Represents the client user's presence
+ */
public readonly presence: ClientPresence;
+
+ /**
+ * Whether or not this account has been verified
+ */
public verified: boolean;
+
+ /**
+ * Edits the logged in client.
+ * @param data The new data
+ */
public edit(data: ClientUserEditData): Promise<this>;
+
+ /**
+ * Sets the activity the client user is playing.
+ * @param name Activity being played, or options for setting the activity
+ * @param options Options for setting the activity
+ * @example
+ * // Set the client user's activity
+ * client.user.setActivity('discord.js', { type: 'WATCHING' });
+ */
public setActivity(options?: ActivityOptions): ClientPresence;
public setActivity(name: string, options?: ActivityOptions): ClientPresence;
+
+ /**
+ * Sets/removes the AFK flag for the client user.
+ * @param afk Whether or not the user is AFK
+ * @param shardId Shard Id(s) to have the AFK flag set on
+ */
public setAFK(afk: boolean, shardId?: number | number[]): ClientPresence;
- public setAvatar(avatar: BufferResolvable | Base64Resolvable): Promise<this>;
+
+ /**
+ * Sets the avatar of the logged in client.
+ * @param avatar The new avatar
+ * @example
+ * // Set avatar
+ * client.user.setAvatar('./avatar.png')
+ * .then(user => console.log(`New avatar set!`))
+ * .catch(console.error);
+ */
+ public setAvatar(avatar: BufferResolvable | Base64Resolvable | null): Promise<this>;
+
+ /**
+ * Sets the full presence of the client user.
+ * @param data Data for the presence
+ * @example
+ * // Set the client user's presence
+ * client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
+ */
public setPresence(data: PresenceData): ClientPresence;
+
+ /**
+ * Sets the status of the client user.
+ * @param status Status to change to
+ * @param shardId Shard id(s) to have the activity set on
+ * @example
+ * // Set the client user's status
+ * client.user.setStatus('idle');
+ */
public setStatus(status: PresenceStatusData, shardId?: number | number[]): ClientPresence;
+
+ /**
+ * Sets the username of the logged in client.
+ * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
+ * every hour. Use this sparingly!</info>
+ * @param username The new username
+ * @example
+ * // Set username
+ * client.user.setUsername('discordjs')
+ * .then(user => console.log(`My new username is ${user.username}`))
+ * .catch(console.error);
+ */
public setUsername(username: string): Promise<this>;
}
diff --git a/src/lib/extensions/discord.js/BushCommandInteraction.ts b/src/lib/extensions/discord.js/BushCommandInteraction.ts
index 174fd9c..e6643f0 100644
--- a/src/lib/extensions/discord.js/BushCommandInteraction.ts
+++ b/src/lib/extensions/discord.js/BushCommandInteraction.ts
@@ -1,19 +1,26 @@
-import type {
- BushApplicationCommand,
- BushGuild,
- BushGuildChannel,
- BushGuildEmoji,
- BushGuildMember,
- BushRole,
- BushUser
-} from '#lib';
+import type { BushApplicationCommand, BushGuild, BushGuildEmoji, BushGuildMember, BushRole, BushUser } from '#lib';
import type { APIInteractionGuildMember } from 'discord-api-types/v9';
import { CommandInteraction, type CacheType, type CacheTypeReducer, type Invite, type Snowflake } from 'discord.js';
import type { RawCommandInteractionData } from 'discord.js/typings/rawDataTypes';
-import { BushGuildTextBasedChannel, BushTextBasedChannels, type BushClient } from '../discord-akairo/BushClient';
+import {
+ BushGuildTextBasedChannel,
+ BushNonThreadGuildBasedChannel,
+ BushTextBasedChannel,
+ type BushClient
+} from '../discord-akairo/BushClient';
-export type BushGuildResolvable = BushGuild | BushGuildChannel | BushGuildMember | BushGuildEmoji | Invite | BushRole | Snowflake;
+export type BushGuildResolvable =
+ | BushGuild
+ | BushNonThreadGuildBasedChannel
+ | BushGuildMember
+ | BushGuildEmoji
+ | Invite
+ | BushRole
+ | Snowflake;
+/**
+ * Represents a command interaction.
+ */
export class BushCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> {
public declare readonly client: BushClient;
public declare readonly command: BushApplicationCommand | BushApplicationCommand<{ guild: BushGuildResolvable }> | null;
@@ -22,7 +29,7 @@ export class BushCommandInteraction<Cached extends CacheType = CacheType> extend
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
- BushTextBasedChannels | null
+ BushTextBasedChannel | null
>;
public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>;
public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>;
diff --git a/src/lib/extensions/discord.js/BushDMChannel.ts b/src/lib/extensions/discord.js/BushDMChannel.ts
index ca08d7e..9df9275 100644
--- a/src/lib/extensions/discord.js/BushDMChannel.ts
+++ b/src/lib/extensions/discord.js/BushDMChannel.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushMessageManager, BushUser } from '#lib';
import { DMChannel } from 'discord.js';
import type { RawDMChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a direct message channel between two users.
+ */
export class BushDMChannel extends DMChannel {
public declare readonly client: BushClient;
public declare messages: BushMessageManager;
diff --git a/src/lib/extensions/discord.js/BushEmoji.ts b/src/lib/extensions/discord.js/BushEmoji.ts
index 5cdf5ac..9e42e5d 100644
--- a/src/lib/extensions/discord.js/BushEmoji.ts
+++ b/src/lib/extensions/discord.js/BushEmoji.ts
@@ -2,6 +2,9 @@ import type { BushClient } from '#lib';
import { Emoji } from 'discord.js';
import type { RawEmojiData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}.
+ */
export class BushEmoji extends Emoji {
public declare readonly client: BushClient;
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 9c272ff..e12053e 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -16,6 +16,11 @@ import { Moderation } from '../../common/Moderation.js';
import { Guild as GuildDB } from '../../models/Guild.js';
import { ModLogType } from '../../models/ModLog.js';
+/**
+ * Represents a guild (or a server) on Discord.
+ * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can
+ * check this with {@link Guild.available}.</info>
+ */
export class BushGuild extends Guild {
public declare readonly client: BushClient;
public declare readonly me: BushGuildMember | null;
@@ -97,6 +102,11 @@ export class BushGuild extends Guild {
return await row.save();
}
+ /**
+ * Get a the log channel configured for a certain log type.
+ * @param logType The type of log channel to get.
+ * @returns Either the log channel or undefined if not configured.
+ */
public async getLogChannel(logType: GuildLogType): Promise<BushTextChannel | undefined> {
const channelId = (await this.getSetting('logChannels'))[logType];
if (!channelId) return undefined;
@@ -107,14 +117,12 @@ export class BushGuild extends Guild {
);
}
- public async bushBan(options: {
- user: BushUserResolvable | UserResolvable;
- reason?: string | null;
- moderator?: BushUserResolvable;
- duration?: number;
- deleteDays?: number;
- evidence?: string;
- }): Promise<'success' | 'missing permissions' | 'error banning' | 'error creating modlog entry' | 'error creating ban entry'> {
+ /**
+ * Bans a user, dms them, creates a mod log entry, and creates a punishment entry.
+ * @param options Options for banning the user.
+ * @returns A string status message of the ban.
+ */
+ public async bushBan(options: BushBanOptions): Promise<BanResponse> {
// checks
if (!this.me!.permissions.has('BAN_MEMBERS')) return 'missing permissions';
@@ -165,18 +173,12 @@ export class BushGuild extends Guild {
return ret;
}
- public async bushUnban(options: {
- user: BushUserResolvable | BushUser;
- reason?: string | null;
- moderator?: BushUserResolvable;
- }): Promise<
- | 'success'
- | 'missing permissions'
- | 'user not banned'
- | 'error unbanning'
- | 'error creating modlog entry'
- | 'error removing ban entry'
- > {
+ /**
+ * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry.
+ * @param options Options for unbanning the user.
+ * @returns A status message of the unban.
+ */
+ public async bushUnban(options: BushUnbanOptions): Promise<UnbanResponse> {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
const user = (await util.resolveNonCachedUser(options.user))!;
@@ -260,3 +262,70 @@ export class BushGuild extends Guild {
void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] });
}
}
+
+/**
+ * Options for unbanning a user
+ */
+interface BushUnbanOptions {
+ /**
+ * The user to unban
+ */
+ user: BushUserResolvable | BushUser;
+
+ /**
+ * The reason for unbanning the user
+ */
+ reason?: string | null;
+
+ /**
+ * The moderator who unbanned the user
+ */
+ moderator?: BushUserResolvable;
+}
+
+/**
+ * Options for banning a user
+ */
+interface BushBanOptions {
+ /**
+ * The user to ban
+ */
+ user: BushUserResolvable | UserResolvable;
+
+ /**
+ * The reason to ban the user
+ */
+ reason?: string | null;
+
+ /**
+ * The moderator who banned the user
+ */
+ moderator?: BushUserResolvable;
+
+ /**
+ * The duration of the ban
+ */
+ duration?: number;
+
+ /**
+ * The number of days to delete the user's messages for
+ */
+ deleteDays?: number;
+
+ /**
+ * The evidence for the ban
+ */
+ evidence?: string;
+}
+
+type PunishmentResponse = 'success' | 'missing permissions' | 'error creating modlog entry';
+
+/**
+ * Response returned when banning a user
+ */
+type BanResponse = PunishmentResponse | 'error banning' | 'error creating ban entry';
+
+/**
+ * Response returned when unbanning a user
+ */
+type UnbanResponse = PunishmentResponse | 'user not banned' | 'error unbanning' | 'error removing ban entry';
diff --git a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts b/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts
index f8e80ae..4d76b07 100644
--- a/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts
+++ b/src/lib/extensions/discord.js/BushGuildApplicationCommandManager.d.ts
@@ -9,15 +9,102 @@ import {
import type { ApplicationCommandData, BaseFetchOptions, Collection, Snowflake } from 'discord.js';
import type { RawApplicationCommandData } from 'discord.js/typings/rawDataTypes';
+/**
+ * An extension for guild-specific application commands.
+ */
export class BushGuildApplicationCommandManager extends BushApplicationCommandManager<BushApplicationCommand, {}, BushGuild> {
public constructor(guild: BushGuild, iterable?: Iterable<RawApplicationCommandData>);
public declare readonly client: BushClient;
+
+ /**
+ * The guild that this manager belongs to
+ */
public guild: BushGuild;
- public create(command: ApplicationCommandData): Promise<BushApplicationCommand>;
+
+ /**
+ * Creates an application command.
+ * @param command The command
+ * @param guildId The guild's id to create this command in,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Create a new command
+ * client.application.commands.create({
+ * name: 'test',
+ * description: 'A test command',
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
+ public create(command: BushApplicationCommandResolvable): Promise<BushApplicationCommand>;
+
+ /**
+ * Deletes an application command.
+ * @param command The command to delete
+ * @param guildId The guild's id where the command is registered,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Delete a command
+ * guild.commands.delete('123456789012345678')
+ * .then(console.log)
+ * .catch(console.error);
+ */
public delete(command: BushApplicationCommandResolvable): Promise<BushApplicationCommand | null>;
+
+ /**
+ * Edits an application command.
+ * @param command The command to edit
+ * @param data The data to update the command with
+ * @param guildId The guild's id where the command registered,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Edit an existing command
+ * client.application.commands.edit('123456789012345678', {
+ * description: 'New description',
+ * })
+ * .then(console.log)
+ * .catch(console.error);
+ */
public edit(command: BushApplicationCommandResolvable, data: ApplicationCommandData): Promise<BushApplicationCommand>;
+
+ /**
+ * Obtains one or multiple application commands from Discord, or the cache if it's already available.
+ * @param id The application command's id
+ * @param options Additional options for this fetch
+ * @example
+ * // Fetch a single command
+ * client.application.commands.fetch('123456789012345678')
+ * .then(command => console.log(`Fetched command ${command.name}`))
+ * .catch(console.error);
+ * @example
+ * // Fetch all commands
+ * guild.commands.fetch()
+ * .then(commands => console.log(`Fetched ${commands.size} commands`))
+ * .catch(console.error);
+ */
public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<BushApplicationCommand>;
public fetch(options: BaseFetchOptions): Promise<Collection<Snowflake, BushApplicationCommand>>;
public fetch(id?: undefined, options?: BaseFetchOptions): Promise<Collection<Snowflake, BushApplicationCommand>>;
+
+ /**
+ * Sets all the commands for this application or guild.
+ * @param commands The commands
+ * @param guildId The guild's id to create the commands in,
+ * ignored when using a {@link GuildApplicationCommandManager}
+ * @example
+ * // Set all commands to just this one
+ * client.application.commands.set([
+ * {
+ * name: 'test',
+ * description: 'A test command',
+ * },
+ * ])
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Remove all commands
+ * guild.commands.set([])
+ * .then(console.log)
+ * .catch(console.error);
+ */
public set(commands: ApplicationCommandData[]): Promise<Collection<Snowflake, BushApplicationCommand>>;
}
diff --git a/src/lib/extensions/discord.js/BushGuildBan.d.ts b/src/lib/extensions/discord.js/BushGuildBan.d.ts
index 4287792..11875f3 100644
--- a/src/lib/extensions/discord.js/BushGuildBan.d.ts
+++ b/src/lib/extensions/discord.js/BushGuildBan.d.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushUser } from '#lib';
import { GuildBan } from 'discord.js';
import type { RawGuildBanData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a ban in a guild on Discord.
+ */
export class BushGuildBan extends GuildBan {
public constructor(client: BushClient, data: RawGuildBanData, guild: BushGuild);
public guild: BushGuild;
diff --git a/src/lib/extensions/discord.js/BushGuildChannel.ts b/src/lib/extensions/discord.js/BushGuildChannel.ts
index 3324dc8..6880daf 100644
--- a/src/lib/extensions/discord.js/BushGuildChannel.ts
+++ b/src/lib/extensions/discord.js/BushGuildChannel.ts
@@ -2,6 +2,15 @@ import type { BushClient, BushGuild } from '#lib';
import { GuildChannel } from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild channel from any of the following:
+ * - {@link BushTextChannel}
+ * - {@link BushVoiceChannel}
+ * - {@link BushCategoryChannel}
+ * - {@link BushNewsChannel}
+ * - {@link BushStoreChannel}
+ * - {@link BushStageChannel}
+ */
export class BushGuildChannel extends GuildChannel {
public declare readonly client: BushClient;
public declare guild: BushGuild;
diff --git a/src/lib/extensions/discord.js/BushGuildEmoji.ts b/src/lib/extensions/discord.js/BushGuildEmoji.ts
index 180f78c..2c9d36b 100644
--- a/src/lib/extensions/discord.js/BushGuildEmoji.ts
+++ b/src/lib/extensions/discord.js/BushGuildEmoji.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildEmojiRoleManager, BushUser } from
import { GuildEmoji } from 'discord.js';
import type { RawGuildEmojiData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a custom emoji.
+ */
export class BushGuildEmoji extends GuildEmoji {
public declare readonly client: BushClient;
public declare guild: BushGuild;
diff --git a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts b/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts
index 3aae4f0..9253cad 100644
--- a/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts
+++ b/src/lib/extensions/discord.js/BushGuildEmojiRoleManager.d.ts
@@ -1,15 +1,51 @@
import type { BushClient, BushGuild, BushGuildEmoji, BushRole, BushRoleResolvable } from '#lib';
import { DataManager, type Collection, type Snowflake } from 'discord.js';
+/**
+ * Manages API methods for roles belonging to emojis and stores their cache.
+ */
export class BushGuildEmojiRoleManager extends DataManager<Snowflake, BushRole, BushRoleResolvable> {
public constructor(emoji: BushGuildEmoji);
public declare readonly client: BushClient;
+
+ /**
+ * The emoji belonging to this manager
+ */
public emoji: BushGuildEmoji;
+
+ /**
+ * The guild belonging to this manager
+ */
public guild: BushGuild;
+
+ /**
+ * Adds a role (or multiple roles) to the list of roles that can use this emoji.
+ * @param roleOrRoles The role or roles to add
+ */
public add(
roleOrRoles: BushRoleResolvable | readonly BushRoleResolvable[] | Collection<Snowflake, BushRole>
): Promise<BushGuildEmoji>;
+
+ /**
+ * Sets the role(s) that can use this emoji.
+ * @param roles The roles or role ids to apply
+ * @example
+ * // Set the emoji's roles to a single role
+ * guildEmoji.roles.set(['391156570408615936'])
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Remove all roles from an emoji
+ * guildEmoji.roles.set([])
+ * .then(console.log)
+ * .catch(console.error);
+ */
public set(roles: readonly BushRoleResolvable[] | Collection<Snowflake, BushRole>): Promise<BushGuildEmoji>;
+
+ /**
+ * Removes a role (or multiple roles) from the list of roles that can use this emoji.
+ * @param roleOrRoles The role or roles to remove
+ */
public remove(
roleOrRoles: BushRoleResolvable | readonly BushRoleResolvable[] | Collection<Snowflake, BushRole>
): Promise<BushGuildEmoji>;
diff --git a/src/lib/extensions/discord.js/BushGuildManager.d.ts b/src/lib/extensions/discord.js/BushGuildManager.d.ts
index 4dd0750..95719a3 100644
--- a/src/lib/extensions/discord.js/BushGuildManager.d.ts
+++ b/src/lib/extensions/discord.js/BushGuildManager.d.ts
@@ -10,9 +10,25 @@ import {
} from 'discord.js';
import { type RawGuildData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for Guilds and stores their cache.
+ */
export class BushGuildManager extends CachedManager<Snowflake, BushGuild, BushGuildResolvable> {
public constructor(client: BushClient, iterable?: Iterable<RawGuildData>);
+
+ /**
+ * Creates a guild.
+ * <warn>This is only available to bots in fewer than 10 guilds.</warn>
+ * @param name The name of the guild
+ * @param options Options for creating the guild
+ * @returns The guild that was created
+ */
public create(name: string, options?: GuildCreateOptions): Promise<BushGuild>;
+
+ /**
+ * Obtains one or multiple guilds from Discord, or the guild cache if it's already available.
+ * @param options The guild's id or options
+ */
public fetch(options: Snowflake | FetchGuildOptions): Promise<BushGuild>;
public fetch(options?: FetchGuildsOptions): Promise<Collection<Snowflake, OAuth2Guild>>;
}
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index fe9c4c9..f6d5259 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -1,79 +1,11 @@
-import { Moderation, ModLogType, type BushClient, type BushGuild, type BushRole, type BushUser } from '#lib';
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { BushClientEvents, Moderation, ModLogType, type BushClient, type BushGuild, type BushRole, type BushUser } from '#lib';
import { GuildMember, MessageEmbed, type Partialize, type Role } from 'discord.js';
import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes';
-interface BushPunishmentOptions {
- reason?: string | null;
- moderator?: BushGuildMember;
- evidence?: string;
-}
-
-interface BushTimedPunishmentOptions extends BushPunishmentOptions {
- duration?: number;
-}
-
-interface AddRoleOptions extends BushTimedPunishmentOptions {
- role: BushRole | Role;
- addToModlog: boolean;
-}
-
-interface RemoveRoleOptions extends BushTimedPunishmentOptions {
- role: BushRole | Role;
- addToModlog: boolean;
-}
-
-type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm';
-
-type WarnResponse = PunishmentResponse;
-
-type AddRoleResponse =
- | PunishmentResponse
- | 'user hierarchy'
- | 'role managed'
- | 'client hierarchy'
- | 'error creating role entry'
- | 'error adding role';
-
-type RemoveRoleResponse =
- | PunishmentResponse
- | 'user hierarchy'
- | 'role managed'
- | 'client hierarchy'
- | 'error removing role entry'
- | 'error removing role';
-
-type MuteResponse =
- | PunishmentResponse
- | 'missing permissions'
- | 'no mute role'
- | 'invalid mute role'
- | 'mute role not manageable'
- | 'error giving mute role'
- | 'error creating mute entry';
-
-type UnmuteResponse =
- | PunishmentResponse
- | 'missing permissions'
- | 'no mute role'
- | 'invalid mute role'
- | 'mute role not manageable'
- | 'error removing mute role'
- | 'error removing mute entry';
-
-type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking';
-
-interface BushBanOptions extends BushTimedPunishmentOptions {
- deleteDays?: number;
-}
-
-type BanResponse = PunishmentResponse | 'missing permissions' | 'error creating ban entry' | 'error banning';
-
-export type PartialBushGuildMember = Partialize<
- BushGuildMember,
- 'joinedAt' | 'joinedTimestamp',
- 'warn' | 'addRole' | 'removeRole' | 'mute' | 'unmute' | 'bushKick' | 'bushBan' | 'isOwner' | 'isSuperUser'
->;
-
+/**
+ * Represents a member of a guild on Discord.
+ */
export class BushGuildMember extends GuildMember {
public declare readonly client: BushClient;
public declare guild: BushGuild;
@@ -83,6 +15,14 @@ export class BushGuildMember extends GuildMember {
super(client, data, guild);
}
+ /**
+ * Send a punishment dm to the user.
+ * @param punishment The punishment that the user has received.
+ * @param reason The reason the user to be punished.
+ * @param duration The duration of the punishment.
+ * @param sendFooter Whether or not to send the guild's punishment footer with the dm.
+ * @returns Whether or not the dm was sent successfully.
+ */
public async punishDM(punishment: string, reason?: string | null, duration?: number, sendFooter = true): Promise<boolean> {
const ending = await this.guild.getSetting('punishmentEnding');
const dmEmbed =
@@ -98,6 +38,12 @@ export class BushGuildMember extends GuildMember {
return !!dmSuccess;
}
+ /**
+ * Warn the user, create a modlog entry, and send a dm to the user.
+ * @param options Options for warning the user.
+ * @returns An object with the result of the warning, and the case number of the warn.
+ * @emits {@link BushClientEvents.bushWarn}
+ */
public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse | null; caseNum: number | null }> {
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
@@ -131,6 +77,12 @@ export class BushGuildMember extends GuildMember {
return ret as { result: WarnResponse | null; caseNum: number | null };
}
+ /**
+ * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment.
+ * @param options Options for adding a role to the user.
+ * @returns A status message for adding the add.
+ * @emits {@link BushClientEvents.bushPunishRole}
+ */
public async addRole(options: AddRoleOptions): Promise<AddRoleResponse> {
const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
if (ifShouldAddRole !== true) return ifShouldAddRole;
@@ -186,6 +138,12 @@ export class BushGuildMember extends GuildMember {
return ret;
}
+ /**
+ * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment.
+ * @param options Options for removing a role from the user.
+ * @returns A status message for removing the role.
+ * @emits {@link BushClientEvents.bushPunishRoleRemove}
+ */
public async removeRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> {
const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator);
if (ifShouldAddRole !== true) return ifShouldAddRole;
@@ -237,11 +195,17 @@ export class BushGuildMember extends GuildMember {
return ret;
}
+ /**
+ * Check whether or not a role should be added/removed from the user based on hierarchy.
+ * @param role The role to check if can be modified.
+ * @param moderator The moderator that is trying to add/remove the role.
+ * @returns `true` if the role should be added/removed or a string for the reason why it shouldn't.
+ */
#checkIfShouldAddRole(
role: BushRole | Role,
moderator?: BushGuildMember
): true | 'user hierarchy' | 'role managed' | 'client hierarchy' {
- if (moderator && moderator.roles.highest.position <= role.position /* && this.guild.ownerId !== this.id */) {
+ if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) {
client.console.debug(`${this.roles.highest.position} <= ${role.position}`);
return 'user hierarchy';
} else if (role.managed) {
@@ -252,6 +216,12 @@ export class BushGuildMember extends GuildMember {
return true;
}
+ /**
+ * Mute the user, create a modlog entry, creates a punishment entry, and dms the user.
+ * @param options Options for muting the user.
+ * @returns A status message for muting the user.
+ * @emits {@link BushClientEvents.bushMute}
+ */
public async mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
// checks
if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions';
@@ -325,6 +295,12 @@ export class BushGuildMember extends GuildMember {
return ret;
}
+ /**
+ * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user.
+ * @param options Options for unmuting the user.
+ * @returns A status message for unmuting the user.
+ * @emits {@link BushClientEvents.bushUnmute}
+ */
public async unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
//checks
if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions';
@@ -393,6 +369,12 @@ export class BushGuildMember extends GuildMember {
return ret;
}
+ /**
+ * Kick the user, create a modlog entry, and dm the user.
+ * @param options Options for kicking the user.
+ * @returns A status message for kicking the user.
+ * @emits {@link BushClientEvents.bushKick}
+ */
public async bushKick(options: BushPunishmentOptions): Promise<KickResponse> {
// checks
if (!this.guild.me?.permissions.has('KICK_MEMBERS') || !this.kickable) return 'missing permissions';
@@ -437,6 +419,12 @@ export class BushGuildMember extends GuildMember {
return ret;
}
+ /**
+ * Ban the user, create a modlog entry, create a punishment entry, and dm the user.
+ * @param options Options for banning the user.
+ * @returns A status message for banning the user.
+ * @emits {@link BushClientEvents.bushBan}
+ */
public async bushBan(options: BushBanOptions): Promise<BanResponse> {
// checks
if (!this.guild.me!.permissions.has('BAN_MEMBERS') || !this.bannable) return 'missing permissions';
@@ -497,11 +485,160 @@ export class BushGuildMember extends GuildMember {
return ret;
}
- public get isOwner(): boolean {
+ /**
+ * Whether or not the user is an owner of the bot.
+ */
+ public isOwner(): boolean {
return client.isOwner(this);
}
- public get isSuperUser(): boolean {
+ /**
+ * Whether or not the user is a super user of the bot.
+ */
+ public isSuperUser(): boolean {
return client.isSuperUser(this);
}
}
+
+/**
+ * Options for punishing a user.
+ */
+interface BushPunishmentOptions {
+ /**
+ * The reason for the punishment.
+ */
+ reason?: string | null;
+
+ /**
+ * The moderator who punished the user.
+ */
+ moderator?: BushGuildMember;
+
+ /**
+ * Evidence for the punishment.
+ */
+ evidence?: string;
+}
+
+/**
+ * Punishment options for punishments that can be temporary.
+ */
+interface BushTimedPunishmentOptions extends BushPunishmentOptions {
+ /**
+ * The duration of the punishment.
+ */
+ duration?: number;
+}
+
+/**
+ * Options for a role add punishment.
+ */
+interface AddRoleOptions extends BushTimedPunishmentOptions {
+ /**
+ * The role to add to the user.
+ */
+ role: BushRole | Role;
+
+ /**
+ * Whether to create a modlog entry for this punishment.
+ */
+ addToModlog: boolean;
+}
+
+/**
+ * Options for a role remove punishment.
+ */
+interface RemoveRoleOptions extends BushTimedPunishmentOptions {
+ /**
+ * The role to remove from the user.
+ */
+ role: BushRole | Role;
+
+ /**
+ * Whether to create a modlog entry for this punishment.
+ */
+ addToModlog: boolean;
+}
+
+/**
+ * Options for banning a user.
+ */
+interface BushBanOptions extends BushTimedPunishmentOptions {
+ /**
+ * The number of days to delete the user's messages for.
+ */
+ deleteDays?: number;
+}
+
+type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm';
+
+/**
+ * Response returned when warning a user.
+ */
+type WarnResponse = PunishmentResponse;
+
+/**
+ * Response returned when adding a role to a user.
+ */
+type AddRoleResponse =
+ | PunishmentResponse
+ | 'user hierarchy'
+ | 'role managed'
+ | 'client hierarchy'
+ | 'error creating role entry'
+ | 'error adding role';
+
+/**
+ * Response returned when removing a role from a user.
+ */
+type RemoveRoleResponse =
+ | PunishmentResponse
+ | 'user hierarchy'
+ | 'role managed'
+ | 'client hierarchy'
+ | 'error removing role entry'
+ | 'error removing role';
+
+/**
+ * Response returned when muting a user.
+ */
+type MuteResponse =
+ | PunishmentResponse
+ | 'missing permissions'
+ | 'no mute role'
+ | 'invalid mute role'
+ | 'mute role not manageable'
+ | 'error giving mute role'
+ | 'error creating mute entry';
+
+/**
+ * Response returned when unmuting a user.
+ */
+type UnmuteResponse =
+ | PunishmentResponse
+ | 'missing permissions'
+ | 'no mute role'
+ | 'invalid mute role'
+ | 'mute role not manageable'
+ | 'error removing mute role'
+ | 'error removing mute entry';
+
+/**
+ * Response returned when kicking a user.
+ */
+type KickResponse = PunishmentResponse | 'missing permissions' | 'error kicking';
+
+/**
+ * Response returned when banning a user.
+ */
+type BanResponse = PunishmentResponse | 'missing permissions' | 'error creating ban entry' | 'error banning';
+
+export type PartialBushGuildMember = Partialize<
+ BushGuildMember,
+ 'joinedAt' | 'joinedTimestamp',
+ 'warn' | 'addRole' | 'removeRole' | 'mute' | 'unmute' | 'bushKick' | 'bushBan' | 'isOwner' | 'isSuperUser'
+>;
+
+/**
+ * @typedef {BushClientEvents} VSCodePleaseDontRemove
+ */ \ No newline at end of file
diff --git a/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts b/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts
index 0866fce..a0e65e7 100644
--- a/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts
+++ b/src/lib/extensions/discord.js/BushGuildMemberManager.d.ts
@@ -14,25 +14,156 @@ import {
} from 'discord.js';
import type { RawGuildMemberData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for GuildMembers and stores their cache.
+ */
export class BushGuildMemberManager extends CachedManager<Snowflake, BushGuildMember, BushGuildMemberResolvable> {
public constructor(guild: BushGuild, iterable?: Iterable<RawGuildMemberData>);
public declare readonly client: BushClient;
+
+ /**
+ * The guild this manager belongs to
+ */
public guild: BushGuild;
+
+ /**
+ * Adds a user to the guild using OAuth2. Requires the `CREATE_INSTANT_INVITE` permission.
+ * @param user The user to add to the guild
+ * @param options Options for adding the user to the guild
+ */
public add(
user: BushUserResolvable,
options: AddGuildMemberOptions & { fetchWhenExisting: false }
): Promise<BushGuildMember | null>;
public add(user: BushUserResolvable, options: AddGuildMemberOptions): Promise<BushGuildMember>;
+
+ /**
+ * Bans a user from the guild.
+ * @param user The user to ban
+ * @param options Options for the ban
+ * @returns Result object will be resolved as specifically as possible.
+ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
+ * be resolved, the user id will be the result.
+ * Internally calls the GuildBanManager#create method.
+ * @example
+ * // Ban a user by id (or with a user/guild member object)
+ * guild.members.ban('84484653687267328')
+ * .then(kickInfo => console.log(`Banned ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`))
+ * .catch(console.error);
+ */
public ban(user: BushUserResolvable, options?: BanOptions): Promise<BushGuildMember | BushUser | Snowflake>;
+
+ /**
+ * Edits a member of the guild.
+ * <info>The user must be a member of the guild</info>
+ * @param user The member to edit
+ * @param data The data to edit the member with
+ * @param reason Reason for editing this user
+ */
public edit(user: BushUserResolvable, data: GuildMemberEditData, reason?: string): Promise<void>;
+
+ /**
+ * Fetches member(s) from Discord, even if they're offline.
+ * @param options If a UserResolvable, the user to fetch.
+ * If undefined, fetches all members.
+ * If a query, it limits the results to users with similar usernames.
+ * @example
+ * // Fetch all members from a guild
+ * guild.members.fetch()
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Fetch a single member
+ * guild.members.fetch('66564597481480192')
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Fetch a single member without checking cache
+ * guild.members.fetch({ user, force: true })
+ * .then(console.log)
+ * .catch(console.error)
+ * @example
+ * // Fetch a single member without caching
+ * guild.members.fetch({ user, cache: false })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Fetch by an array of users including their presences
+ * guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Fetch by query
+ * guild.members.fetch({ query: 'hydra', limit: 1 })
+ * .then(console.log)
+ * .catch(console.error);
+ */
public fetch(
options: BushUserResolvable | FetchMemberOptions | (FetchMembersOptions & { user: BushUserResolvable })
): Promise<BushGuildMember>;
public fetch(options?: FetchMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>;
+
+ /**
+ * Kicks a user from the guild.
+ * <info>The user must be a member of the guild</info>
+ * @param user The member to kick
+ * @param reason Reason for kicking
+ * @returns Result object will be resolved as specifically as possible.
+ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
+ * be resolved, the user's id will be the result.
+ * @example
+ * // Kick a user by id (or with a user/guild member object)
+ * guild.members.kick('84484653687267328')
+ * .then(banInfo => console.log(`Kicked ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
+ * .catch(console.error);
+ */
public kick(user: BushUserResolvable, reason?: string): Promise<BushGuildMember | BushUser | Snowflake>;
+
+ /**
+ * Lists up to 1000 members of the guild.
+ * @param options Options for listing members
+ */
public list(options?: GuildListMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>;
+
+ /**
+ * Prunes members from the guild based on how long they have been inactive.
+ * @param options Options for pruning
+ * @returns The number of members that were/will be kicked
+ * @example
+ * // See how many members will be pruned
+ * guild.members.prune({ dry: true })
+ * .then(pruned => console.log(`This will prune ${pruned} people!`))
+ * .catch(console.error);
+ * @example
+ * // Actually prune the members
+ * guild.members.prune({ days: 1, reason: 'too many people!' })
+ * .then(pruned => console.log(`I just pruned ${pruned} people!`))
+ * .catch(console.error);
+ * @example
+ * // Include members with a specified role
+ * guild.members.prune({ days: 7, roles: ['657259391652855808'] })
+ * .then(pruned => console.log(`I just pruned ${pruned} people!`))
+ * .catch(console.error);
+ */
public prune(options: GuildPruneMembersOptions & { dry?: false; count: false }): Promise<null>;
public prune(options?: GuildPruneMembersOptions): Promise<number>;
+
+ /**
+ * Searches for members in the guild based on a query.
+ * @param options Options for searching members
+ */
public search(options: GuildSearchMembersOptions): Promise<Collection<Snowflake, BushGuildMember>>;
+
+ /**
+ * Unbans a user from the guild. Internally calls the {@link GuildBanManager.remove} method.
+ * @param user The user to unban
+ * @param reason Reason for unbanning user
+ * @returns The user that was unbanned
+ * @example
+ * // Unban a user by id (or with a user/guild member object)
+ * guild.members.unban('84484653687267328')
+ * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
+ * .catch(console.error);
+ */
public unban(user: BushUserResolvable, reason?: string): Promise<BushUser>;
}
diff --git a/src/lib/extensions/discord.js/BushMessage.ts b/src/lib/extensions/discord.js/BushMessage.ts
index 9f6d422..b442196 100644
--- a/src/lib/extensions/discord.js/BushMessage.ts
+++ b/src/lib/extensions/discord.js/BushMessage.ts
@@ -4,7 +4,7 @@ import type {
BushGuild,
BushGuildMember,
BushGuildTextBasedChannel,
- BushTextBasedChannels,
+ BushTextBasedChannel,
BushUser
} from '#lib';
import { Message, type If, type Partialize } from 'discord.js';
@@ -15,19 +15,23 @@ export type PartialBushMessage = Partialize<
'type' | 'system' | 'pinned' | 'tts',
'content' | 'cleanContent' | 'author'
>;
+
+/**
+ * Represents a message on Discord.
+ */
export class BushMessage<Cached extends boolean = boolean> extends Message<Cached> {
public declare readonly client: BushClient;
public declare util: BushCommandUtil<BushMessage<true>>;
public declare readonly guild: If<Cached, BushGuild>;
public declare readonly member: BushGuildMember | null;
public declare author: BushUser;
- public declare readonly channel: If<Cached, BushGuildTextBasedChannel, BushTextBasedChannels>;
+ public declare readonly channel: If<Cached, BushGuildTextBasedChannel, BushTextBasedChannel>;
public constructor(client: BushClient, data: RawMessageData) {
super(client, data);
}
}
-export interface BushMessage {
+export interface BushMessage<Cached extends boolean = boolean> extends Message<Cached> {
fetch(force?: boolean): Promise<BushMessage>;
}
diff --git a/src/lib/extensions/discord.js/BushMessageManager.d.ts b/src/lib/extensions/discord.js/BushMessageManager.d.ts
index f6bc8e7..84918c0 100644
--- a/src/lib/extensions/discord.js/BushMessageManager.d.ts
+++ b/src/lib/extensions/discord.js/BushMessageManager.d.ts
@@ -1,4 +1,4 @@
-import { BushMessageResolvable, type BushMessage, type BushTextBasedChannels } from '#lib';
+import { BushMessageResolvable, BushTextBasedChannel, type BushMessage } from '#lib';
import {
CachedManager,
type BaseFetchOptions,
@@ -11,17 +11,95 @@ import {
} from 'discord.js';
import type { RawMessageData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for Messages and holds their cache.
+ */
export class BushMessageManager extends CachedManager<Snowflake, BushMessage, BushMessageResolvable> {
- public constructor(channel: BushTextBasedChannels, iterable?: Iterable<RawMessageData>);
- public channel: BushTextBasedChannels;
+ public constructor(channel: BushTextBasedChannel, iterable?: Iterable<RawMessageData>);
+
+ /**
+ * The channel that the messages belong to
+ */
+ public channel: BushTextBasedChannel;
+
+ /**
+ * The cache of Messages
+ */
public cache: Collection<Snowflake, BushMessage>;
+
+ /**
+ * Publishes a message in an announcement channel to all channels following it, even if it's not cached.
+ * @param message The message to publish
+ */
public crosspost(message: BushMessageResolvable): Promise<BushMessage>;
+
+ /**
+ * Deletes a message, even if it's not cached.
+ * @param message The message to delete
+ */
public delete(message: BushMessageResolvable): Promise<void>;
+
+ /**
+ * Edits a message, even if it's not cached.
+ * @param message The message to edit
+ * @param options The options to edit the message
+ */
public edit(message: BushMessageResolvable, options: MessagePayload | MessageEditOptions): Promise<BushMessage>;
+
+ /**
+ * Gets a message, or messages, from this channel.
+ * <info>The returned Collection does not contain reaction users of the messages if they were not cached.
+ * Those need to be fetched separately in such a case.</info>
+ * @param message The id of the message to fetch, or query parameters.
+ * @param options Additional options for this fetch
+ * @example
+ * // Get message
+ * channel.messages.fetch('99539446449315840')
+ * .then(message => console.log(message.content))
+ * .catch(console.error);
+ * @example
+ * // Get messages
+ * channel.messages.fetch({ limit: 10 })
+ * .then(messages => console.log(`Received ${messages.size} messages`))
+ * .catch(console.error);
+ * @example
+ * // Get messages and filter by user id
+ * channel.messages.fetch()
+ * .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
+ * .catch(console.error);
+ */
public fetch(message: Snowflake, options?: BaseFetchOptions): Promise<BushMessage>;
public fetch(options?: ChannelLogsQueryOptions, cacheOptions?: BaseFetchOptions): Promise<Collection<Snowflake, BushMessage>>;
+
+ /**
+ * Fetches the pinned messages of this channel and returns a collection of them.
+ * <info>The returned Collection does not contain any reaction data of the messages.
+ * Those need to be fetched separately.</info>
+ * @param cache Whether to cache the message(s)
+ * @example
+ * // Get pinned messages
+ * channel.messages.fetchPinned()
+ * .then(messages => console.log(`Received ${messages.size} messages`))
+ * .catch(console.error);
+ */
public fetchPinned(cache?: boolean): Promise<Collection<Snowflake, BushMessage>>;
+
+ /**
+ * Adds a reaction to a message, even if it's not cached.
+ * @param message The message to react to
+ * @param emoji The emoji to react with
+ */
public react(message: BushMessageResolvable, emoji: EmojiIdentifierResolvable): Promise<void>;
+
+ /**
+ * Pins a message to the channel's pinned messages, even if it's not cached.
+ * @param message The message to pin
+ */
public pin(message: BushMessageResolvable): Promise<void>;
+
+ /**
+ * Unpins a message from the channel's pinned messages, even if it's not cached.
+ * @param message The message to unpin
+ */
public unpin(message: BushMessageResolvable): Promise<void>;
}
diff --git a/src/lib/extensions/discord.js/BushMessageReaction.ts b/src/lib/extensions/discord.js/BushMessageReaction.ts
index 51b439a..47d4119 100644
--- a/src/lib/extensions/discord.js/BushMessageReaction.ts
+++ b/src/lib/extensions/discord.js/BushMessageReaction.ts
@@ -4,6 +4,9 @@ import type { RawMessageReactionData } from 'discord.js/typings/rawDataTypes';
export type PartialBushMessageReaction = Partialize<BushMessageReaction, 'count'>;
+/**
+ * Represents a reaction to a message.
+ */
export class BushMessageReaction extends MessageReaction {
public declare readonly client: BushClient;
public declare readonly emoji: BushGuildEmoji | BushReactionEmoji;
diff --git a/src/lib/extensions/discord.js/BushNewsChannel.ts b/src/lib/extensions/discord.js/BushNewsChannel.ts
index 716c57d..7df7f37 100644
--- a/src/lib/extensions/discord.js/BushNewsChannel.ts
+++ b/src/lib/extensions/discord.js/BushNewsChannel.ts
@@ -1,6 +1,9 @@
import type { BushGuild, BushGuildMember, BushMessageManager, BushThreadManager } from '#lib';
import { NewsChannel, type AllowedThreadTypeForNewsChannel, type Collection, type Snowflake } from 'discord.js';
+/**
+ * Represents a guild news channel on Discord.
+ */
export class BushNewsChannel extends NewsChannel {
public declare threads: BushThreadManager<AllowedThreadTypeForNewsChannel>;
public declare guild: BushGuild;
diff --git a/src/lib/extensions/discord.js/BushPresence.ts b/src/lib/extensions/discord.js/BushPresence.ts
index 60408f0..f0a3ba6 100644
--- a/src/lib/extensions/discord.js/BushPresence.ts
+++ b/src/lib/extensions/discord.js/BushPresence.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildMember, BushUser } from '#lib';
import { Presence } from 'discord.js';
import type { RawPresenceData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a user's presence.
+ */
export class BushPresence extends Presence {
public declare guild: BushGuild | null;
public declare readonly member: BushGuildMember | null;
diff --git a/src/lib/extensions/discord.js/BushReactionEmoji.ts b/src/lib/extensions/discord.js/BushReactionEmoji.ts
index b85916c..b2a7eb0 100644
--- a/src/lib/extensions/discord.js/BushReactionEmoji.ts
+++ b/src/lib/extensions/discord.js/BushReactionEmoji.ts
@@ -2,6 +2,11 @@ import type { BushMessageReaction } from '#lib';
import { ReactionEmoji } from 'discord.js';
import type { RawReactionEmojiData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis
+ * will use this class opposed to the Emoji class when the client doesn't know enough
+ * information about them.
+ */
export class BushReactionEmoji extends ReactionEmoji {
public declare reaction: BushMessageReaction;
diff --git a/src/lib/extensions/discord.js/BushRole.ts b/src/lib/extensions/discord.js/BushRole.ts
index bf09e2d..acf795d 100644
--- a/src/lib/extensions/discord.js/BushRole.ts
+++ b/src/lib/extensions/discord.js/BushRole.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildMember } from '#lib';
import { Role, type Collection, type Snowflake } from 'discord.js';
import type { RawRoleData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a role on Discord.
+ */
export class BushRole extends Role {
public declare guild: BushGuild;
public declare readonly members: Collection<Snowflake, BushGuildMember>;
diff --git a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts
index d33ddd3..903b43f 100644
--- a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts
+++ b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts
@@ -1,15 +1,18 @@
-import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannels, BushUser } from '#lib';
+import type { BushClient, BushGuild, BushGuildMember, BushGuildTextBasedChannel, BushTextBasedChannel, BushUser } from '#lib';
import type { APIInteractionGuildMember } from 'discord-api-types/v9';
import { SelectMenuInteraction, type CacheType, type CacheTypeReducer } from 'discord.js';
import type { RawMessageSelectMenuInteractionData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a select menu interaction.
+ */
export class BushSelectMenuInteraction<Cached extends CacheType = CacheType> extends SelectMenuInteraction<Cached> {
public declare readonly channel: CacheTypeReducer<
Cached,
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
BushGuildTextBasedChannel | null,
- BushTextBasedChannels | null
+ BushTextBasedChannel | null
>;
public declare readonly guild: CacheTypeReducer<Cached, BushGuild, null>;
public declare member: CacheTypeReducer<Cached, BushGuildMember, APIInteractionGuildMember>;
diff --git a/src/lib/extensions/discord.js/BushStageChannel.ts b/src/lib/extensions/discord.js/BushStageChannel.ts
index 1391757..5f6c581 100644
--- a/src/lib/extensions/discord.js/BushStageChannel.ts
+++ b/src/lib/extensions/discord.js/BushStageChannel.ts
@@ -2,11 +2,14 @@ import type { BushCategoryChannel, BushGuild, BushGuildMember, BushStageInstance
import { StageChannel, type Collection, type Snowflake } from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild stage channel on Discord.
+ */
export class BushStageChannel extends StageChannel {
- public declare readonly instance: BushStageInstance | null;
public declare readonly members: Collection<Snowflake, BushGuildMember>;
public declare guild: BushGuild;
public declare readonly parent: BushCategoryChannel | null;
+ public declare readonly stageInstance: BushStageInstance | null;
public constructor(guild: BushGuild, data?: RawGuildChannelData) {
super(guild, data);
diff --git a/src/lib/extensions/discord.js/BushStageInstance.ts b/src/lib/extensions/discord.js/BushStageInstance.ts
index 3b46bf6..80fa5cf 100644
--- a/src/lib/extensions/discord.js/BushStageInstance.ts
+++ b/src/lib/extensions/discord.js/BushStageInstance.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushStageChannel } from '#lib';
import { StageInstance } from 'discord.js';
import type { RawStageInstanceData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a stage instance.
+ */
export class BushStageInstance extends StageInstance {
public declare readonly channel: BushStageChannel | null;
public declare readonly guild: BushGuild | null;
diff --git a/src/lib/extensions/discord.js/BushStoreChannel.ts b/src/lib/extensions/discord.js/BushStoreChannel.ts
index 918c27b..cb75076 100644
--- a/src/lib/extensions/discord.js/BushStoreChannel.ts
+++ b/src/lib/extensions/discord.js/BushStoreChannel.ts
@@ -2,6 +2,10 @@ import type { BushCategoryChannel, BushClient, BushGuild, BushGuildMember } from
import { StoreChannel, type Collection, type Snowflake } from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild store channel on Discord.
+ * @deprecated Store channels are deprecated and will be removed from Discord in March 2022. See [Self-serve Game Selling Deprecation](https://support-dev.discord.com/hc/en-us/articles/4414590563479) for more information
+ */
// eslint-disable-next-line deprecation/deprecation
export class BushStoreChannel extends StoreChannel {
public declare guild: BushGuild;
diff --git a/src/lib/extensions/discord.js/BushTextChannel.ts b/src/lib/extensions/discord.js/BushTextChannel.ts
index 57f0833..45e1200 100644
--- a/src/lib/extensions/discord.js/BushTextChannel.ts
+++ b/src/lib/extensions/discord.js/BushTextChannel.ts
@@ -2,6 +2,9 @@ import type { BushGuild, BushMessageManager, BushThreadManager } from '#lib';
import { TextChannel, type AllowedThreadTypeForTextChannel } from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild text channel on Discord.
+ */
export class BushTextChannel extends TextChannel {
public declare guild: BushGuild;
public declare messages: BushMessageManager;
diff --git a/src/lib/extensions/discord.js/BushThreadChannel.ts b/src/lib/extensions/discord.js/BushThreadChannel.ts
index 5f68213..4310e83 100644
--- a/src/lib/extensions/discord.js/BushThreadChannel.ts
+++ b/src/lib/extensions/discord.js/BushThreadChannel.ts
@@ -10,6 +10,9 @@ import type {
import { ThreadChannel, type Collection, type Snowflake } from 'discord.js';
import type { RawThreadChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a thread channel on Discord.
+ */
export class BushThreadChannel extends ThreadChannel {
public declare guild: BushGuild;
public declare messages: BushMessageManager;
diff --git a/src/lib/extensions/discord.js/BushThreadManager.d.ts b/src/lib/extensions/discord.js/BushThreadManager.d.ts
index c824ae7..1366d68 100644
--- a/src/lib/extensions/discord.js/BushThreadManager.d.ts
+++ b/src/lib/extensions/discord.js/BushThreadManager.d.ts
@@ -14,12 +14,69 @@ import {
} from 'discord.js';
import type { RawThreadChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for {@link BushThreadChannel} objects and stores their cache.
+ */
export class BushThreadManager<AllowedThreadType> extends CachedManager<Snowflake, BushThreadChannel, ThreadChannelResolvable> {
public constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>);
+
+ /**
+ * The channel this Manager belongs to
+ */
public channel: TextChannel | NewsChannel;
+
+ /**
+ * Creates a new thread in the channel.
+ * @param options Options to create a new thread
+ * @example
+ * // Create a new public thread
+ * channel.threads
+ * .create({
+ * name: 'food-talk',
+ * autoArchiveDuration: 60,
+ * reason: 'Needed a separate thread for food',
+ * })
+ * .then(threadChannel => console.log(threadChannel))
+ * .catch(console.error);
+ * @example
+ * // Create a new private thread
+ * channel.threads
+ * .create({
+ * name: 'mod-talk',
+ * autoArchiveDuration: 60,
+ * type: 'GUILD_PRIVATE_THREAD',
+ * reason: 'Needed a separate thread for moderation',
+ * })
+ * .then(threadChannel => console.log(threadChannel))
+ * .catch(console.error);
+ */
public create(options: ThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>;
+
+ /**
+ * Obtains a thread from Discord, or the channel cache if it's already available.
+ * @param options The options to fetch threads. If it is a
+ * ThreadChannelResolvable then the specified thread will be fetched. Fetches all active threads if `undefined`
+ * @param cacheOptions Additional options for this fetch. <warn>The `force` field gets ignored
+ * if `options` is not a {@link ThreadChannelResolvable}</warn>
+ * @example
+ * // Fetch a thread by its id
+ * channel.threads.fetch('831955138126104859')
+ * .then(channel => console.log(channel.name))
+ * .catch(console.error);
+ */
public fetch(options: ThreadChannelResolvable, cacheOptions?: BaseFetchOptions): Promise<ThreadChannel | null>;
public fetch(options?: FetchThreadsOptions, cacheOptions?: { cache?: boolean }): Promise<FetchedThreads>;
+
+ /**
+ * Obtains a set of archived threads from Discord, requires `READ_MESSAGE_HISTORY` in the parent channel.
+ * @param options The options to fetch archived threads
+ * @param cache Whether to cache the new thread objects if they aren't already
+ */
public fetchArchived(options?: FetchArchivedThreadOptions, cache?: boolean): Promise<FetchedThreads>;
+
+ /**
+ * Obtains the accessible active threads from Discord, requires `READ_MESSAGE_HISTORY` in the parent channel.
+ * @param cache Whether to cache the new thread objects if they aren't already
+ */
public fetchActive(cache?: boolean): Promise<FetchedThreads>;
}
diff --git a/src/lib/extensions/discord.js/BushThreadMember.ts b/src/lib/extensions/discord.js/BushThreadMember.ts
index fa7588e..a316046 100644
--- a/src/lib/extensions/discord.js/BushThreadMember.ts
+++ b/src/lib/extensions/discord.js/BushThreadMember.ts
@@ -2,6 +2,9 @@ import type { BushGuildMember, BushThreadChannel, BushUser } from '#lib';
import { ThreadMember } from 'discord.js';
import type { RawThreadMemberData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a Member for a Thread.
+ */
export class BushThreadMember extends ThreadMember {
public declare readonly guildMember: BushGuildMember | null;
public declare readonly user: BushUser | null;
diff --git a/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts b/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts
index 7560c0e..dedf102 100644
--- a/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts
+++ b/src/lib/extensions/discord.js/BushThreadMemberManager.d.ts
@@ -2,13 +2,42 @@ import type { BushClient, BushThreadChannel, BushThreadMember, BushThreadMemberR
import { CachedManager, type BaseFetchOptions, type Collection, type Snowflake, type UserResolvable } from 'discord.js';
import type { RawThreadMemberData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for GuildMembers and stores their cache.
+ */
export class BushThreadMemberManager extends CachedManager<Snowflake, BushThreadMember, BushThreadMemberResolvable> {
public constructor(thread: BushThreadChannel, iterable?: Iterable<RawThreadMemberData>);
public declare readonly client: BushClient;
+
+ /**
+ * The thread this manager belongs to
+ */
public thread: BushThreadChannel;
+
+ /**
+ * Adds a member to the thread.
+ * @param member The member to add
+ * @param reason The reason for adding this member
+ */
public add(member: UserResolvable | '@me', reason?: string): Promise<Snowflake>;
+
+ /**
+ * Fetches member(s) for the thread from Discord, requires access to the `GUILD_MEMBERS` gateway intent.
+ * @param member The member to fetch. If `undefined`, all members in the thread are fetched, and will be
+ * cached based on `options.cache`. If boolean, this serves the purpose of `options.cache`.
+ * @param options Additional options for this fetch
+ */
public fetch(member?: UserResolvable, options?: BaseFetchOptions): Promise<BushThreadMember>;
- /** @deprecated Use `fetch(member, options)` instead. */
+
+ /**
+ * @deprecated Use `fetch(member, options)` instead.
+ */
public fetch(cache?: boolean): Promise<Collection<Snowflake, BushThreadMember>>;
+
+ /**
+ * Remove a user from the thread.
+ * @param id The id of the member to remove
+ * @param reason The reason for removing this member from the thread
+ */
public remove(id: Snowflake | '@me', reason?: string): Promise<Snowflake>;
}
diff --git a/src/lib/extensions/discord.js/BushUser.ts b/src/lib/extensions/discord.js/BushUser.ts
index 9b2c92a..5ab288e 100644
--- a/src/lib/extensions/discord.js/BushUser.ts
+++ b/src/lib/extensions/discord.js/BushUser.ts
@@ -4,6 +4,9 @@ import type { RawUserData } from 'discord.js/typings/rawDataTypes';
export type PartialBushUser = Partialize<BushUser, 'username' | 'tag' | 'discriminator' | 'isOwner' | 'isSuperUser'>;
+/**
+ * Represents a user on Discord.
+ */
export class BushUser extends User {
public declare readonly client: BushClient;
public declare readonly dmChannel: BushDMChannel | null;
@@ -12,10 +15,16 @@ export class BushUser extends User {
super(client, data);
}
+ /**
+ * Indicates whether the user is an owner of the bot.
+ */
public isOwner(): boolean {
return client.isOwner(this);
}
+ /**
+ * Indicates whether the user is a superuser of the bot.
+ */
public isSuperUser(): boolean {
return client.isSuperUser(this);
}
diff --git a/src/lib/extensions/discord.js/BushUserManager.d.ts b/src/lib/extensions/discord.js/BushUserManager.d.ts
index 595332a..5d814da 100644
--- a/src/lib/extensions/discord.js/BushUserManager.d.ts
+++ b/src/lib/extensions/discord.js/BushUserManager.d.ts
@@ -1,8 +1,59 @@
-import type { BushClient, BushUser, BushUserResolvable } from '#lib';
-import { CachedManager, type BaseFetchOptions, type Snowflake } from 'discord.js';
+import type { BushClient, BushDMChannel, BushUser, BushUserResolvable } from '#lib';
+import {
+ CachedManager,
+ Message,
+ MessageOptions,
+ MessagePayload,
+ UserFlags,
+ type BaseFetchOptions,
+ type Snowflake
+} from 'discord.js';
import type { RawUserData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Manages API methods for users and stores their cache.
+ */
export class BushUserManager extends CachedManager<Snowflake, BushUser, BushUserResolvable> {
- public constructor(client: BushClient, iterable?: Iterable<RawUserData>);
- public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<BushUser>;
+ private constructor(client: BushClient, iterable?: Iterable<RawUserData>);
+
+ /**
+ * The DM between the client's user and a user
+ * @param userId The user id
+ * @private
+ */
+ public dmChannel(userId: Snowflake): BushDMChannel | null;
+
+ /**
+ * Creates a {@link DMChannel} between the client and a user.
+ * @param user The UserResolvable to identify
+ * @param options Additional options for this fetch
+ */
+ public createDM(user: BushUserResolvable, options?: BaseFetchOptions): Promise<BushDMChannel>;
+
+ /**
+ * Deletes a {@link DMChannel} (if one exists) between the client and a user. Resolves with the channel if successful.
+ * @param user The UserResolvable to identify
+ */
+ public deleteDM(user: BushUserResolvable): Promise<BushDMChannel>;
+
+ /**
+ * Obtains a user from Discord, or the user cache if it's already available.
+ * @param user The user to fetch
+ * @param options Additional options for this fetch
+ */
+ public fetch(user: BushUserResolvable, options?: BaseFetchOptions): Promise<BushUser>;
+
+ /**
+ * Fetches a user's flags.
+ * @param user The UserResolvable to identify
+ * @param options Additional options for this fetch
+ */
+ public fetchFlags(user: BushUserResolvable, options?: BaseFetchOptions): Promise<UserFlags>;
+
+ /**
+ * Sends a message to a user.
+ * @param user The UserResolvable to identify
+ * @param options The options to provide
+ */
+ public send(user: BushUserResolvable, options: string | MessagePayload | MessageOptions): Promise<Message>;
}
diff --git a/src/lib/extensions/discord.js/BushVoiceChannel.ts b/src/lib/extensions/discord.js/BushVoiceChannel.ts
index d5cdafc..9f246e5 100644
--- a/src/lib/extensions/discord.js/BushVoiceChannel.ts
+++ b/src/lib/extensions/discord.js/BushVoiceChannel.ts
@@ -2,6 +2,9 @@ import type { BushClient, BushGuild, BushGuildMember } from '#lib';
import { VoiceChannel, type Collection, type Snowflake } from 'discord.js';
import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents a guild voice channel on Discord.
+ */
export class BushVoiceChannel extends VoiceChannel {
public declare readonly client: BushClient;
public declare readonly members: Collection<Snowflake, BushGuildMember>;
diff --git a/src/lib/extensions/discord.js/BushVoiceState.ts b/src/lib/extensions/discord.js/BushVoiceState.ts
index a479143..3f19201 100644
--- a/src/lib/extensions/discord.js/BushVoiceState.ts
+++ b/src/lib/extensions/discord.js/BushVoiceState.ts
@@ -1,10 +1,13 @@
-import type { BushGuild, BushGuildMember, BushStageChannel, BushVoiceChannel } from '#lib';
+import type { BushClient, BushGuild, BushGuildMember, BushVoiceBasedChannel } from '#lib';
import { VoiceState } from 'discord.js';
import type { RawVoiceStateData } from 'discord.js/typings/rawDataTypes';
+/**
+ * Represents the voice state for a Guild Member.
+ */
export class BushVoiceState extends VoiceState {
- // public declare readonly client: BushClient;
- public declare readonly channel: BushVoiceChannel | BushStageChannel | null;
+ public declare readonly client: BushClient;
+ public declare readonly channel: BushVoiceBasedChannel | null;
public declare guild: BushGuild;
public declare readonly member: BushGuildMember | null;
diff --git a/src/lib/extensions/global.d.ts b/src/lib/extensions/global.d.ts
index 1df86bb..a6f2b5a 100644
--- a/src/lib/extensions/global.d.ts
+++ b/src/lib/extensions/global.d.ts
@@ -1,7 +1,14 @@
/* eslint-disable no-var */
import type { BushClient, BushClientUtil } from '#lib';
declare global {
+ /**
+ * The bushbot client.
+ */
var client: BushClient;
+
+ /**
+ * The bushbot client util.
+ */
var util: BushClientUtil;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
diff --git a/src/lib/utils/BushCache.ts b/src/lib/utils/BushCache.ts
index bfbd7e3..476f516 100644
--- a/src/lib/utils/BushCache.ts
+++ b/src/lib/utils/BushCache.ts
@@ -3,7 +3,7 @@ import { Guild } from '../models/Guild.js';
export class BushCache {
public global = new GlobalCache();
- public guilds = new Collection<Snowflake, Guild>();
+ public guilds = new GuildCache();
}
export class GlobalCache {
@@ -13,3 +13,5 @@ export class GlobalCache {
public blacklistedGuilds: Snowflake[] = [];
public blacklistedUsers: Snowflake[] = [];
}
+
+export class GuildCache extends Collection<Snowflake, Guild> {}
diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts
index 434a0a7..297a641 100644
--- a/src/lib/utils/BushConstants.ts
+++ b/src/lib/utils/BushConstants.ts
@@ -168,7 +168,9 @@ export class BushConstants {
CREATE_PRIVATE_THREADS: { name: 'Create Private Threads', important: false },
USE_EXTERNAL_STICKERS: { name: 'Use External Stickers', important: false },
SEND_MESSAGES_IN_THREADS: { name: 'Send Messages In Threads', important: false },
- START_EMBEDDED_ACTIVITIES: { name: 'Start Activities', important: false }
+ START_EMBEDDED_ACTIVITIES: { name: 'Start Activities', important: false },
+ MODERATE_MEMBERS: { name: 'Moderate Members', important: true },
+ MANAGE_EVENTS: { name: 'Manage Events', important: true },
},
// prettier-ignore
diff --git a/src/lib/utils/BushLogger.ts b/src/lib/utils/BushLogger.ts
index 5bae96d..43c9203 100644
--- a/src/lib/utils/BushLogger.ts
+++ b/src/lib/utils/BushLogger.ts
@@ -1,10 +1,21 @@
+import * as assert from 'assert';
import chalk from 'chalk';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { MessageEmbed, Util, type Message, type PartialTextBasedChannelFields } from 'discord.js';
import { inspect } from 'util';
import { type BushSendMessageType } from '../extensions/discord-akairo/BushClient';
+/**
+ * A custom logging utility for the bot.
+ */
export class BushLogger {
+ /**
+ * Parses the content surrounding by `<<>>` and emphasizes it with the given color or by making it bold.
+ * @param content The content to parse.
+ * @param color The color to emphasize the content with.
+ * @param discordFormat Whether or not to format the content for discord.
+ * @returns The formatted content.
+ */
static #parseFormatting(
content: any,
color: 'blueBright' | 'blackBright' | 'redBright' | 'yellowBright' | 'greenBright' | '',
@@ -23,6 +34,13 @@ export class BushLogger {
return tempParsedArray.join('');
}
+ /**
+ * Inspects the content and returns a string.
+ * @param content The content to inspect.
+ * @param depth The depth the content will inspected. Defaults to `2`.
+ * @param colors Whether or not to use colors in the output. Defaults to `true`.
+ * @returns The inspected content.
+ */
static #inspectContent(content: any, depth = 2, colors = true): string {
if (typeof content !== 'string') {
return inspect(content, { depth, colors });
@@ -30,6 +48,11 @@ export class BushLogger {
return content;
}
+ /**
+ * Strips ANSI color codes from a string.
+ * @param text The string to strip color codes from.
+ * @returns A string without ANSI color codes.
+ */
static #stripColor(text: string): string {
return text.replace(
// eslint-disable-next-line no-control-regex
@@ -38,6 +61,10 @@ export class BushLogger {
);
}
+ /**
+ * Generates a formatted timestamp for logging.
+ * @returns The formatted timestamp.
+ */
static #getTimeStamp(): string {
const now = new Date();
const hours = now.getHours();
@@ -63,26 +90,39 @@ export class BushLogger {
}
/**
- * Sends a message to the log channel
- * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}
+ * Sends a message to the log channel.
+ * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
+ * @returns The message sent.
*/
- public static async channelLog(message: BushSendMessageType) {
+ public static async channelLog(message: BushSendMessageType): Promise<Message | null> {
const channel = await util.getConfigChannel('log');
- await channel.send(message).catch(() => {});
+ return await channel.send(message).catch(() => null);
}
/**
- * Sends a message to the error channel
+ * Sends a message to the error channel.
+ * @param message The parameter to pass to {@link PartialTextBasedChannelFields.send}.
+ * @returns The message sent.
*/
- public static async channelError(message: BushSendMessageType): Promise<Message> {
+ public static async channelError(message: BushSendMessageType): Promise<Message | null> {
const channel = await util.getConfigChannel('error');
+ if (!channel) {
+ void this.error(
+ 'BushLogger',
+ `Could not find error channel, was originally going to send: \n${util.inspect(message, {
+ colors: true
+ })}\n${new Error().stack?.substring(8)}`,
+ false
+ );
+ return null;
+ }
return await channel.send(message);
}
/**
* Logs debug information. Only works in dev is enabled in the config.
* @param content The content to log.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
public static debug(content: any, depth = 0): void {
if (!client.config.isDevelopment) return;
@@ -103,10 +143,10 @@ export class BushLogger {
* Logs verbose information. Highlight information by surrounding it in `<<>>`.
* @param header The header printed before the content, displayed in grey.
* @param content The content to log, highlights displayed in bright black.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param sendChannel Should this also be logged to discord? Defaults to `false`.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
- public static async verbose(header: string, content: any, sendChannel = false, depth = 0) {
+ public static async verbose(header: string, content: any, sendChannel = false, depth = 0): Promise<void> {
if (!client.config.logging.verbose) return;
const newContent = this.#inspectContent(content, depth, true);
console.info(
@@ -121,13 +161,30 @@ export class BushLogger {
}
/**
+ * Logs very verbose information. Highlight information by surrounding it in `<<>>`.
+ * @param header The header printed before the content, displayed in grey.
+ * @param content The content to log, highlights displayed in bright black.
+ * @param depth The depth the content will inspected. Defaults to `0`.
+ */
+ public static async superVerbose(header: string, content: any, depth = 0): Promise<void> {
+ if (!client.config.logging.verbose) return;
+ const newContent = this.#inspectContent(content, depth, true);
+ console.info(
+ `${chalk.bgHex('#8423b8')(this.#getTimeStamp())} ${chalk.hex('#8423b8')(`[${header}]`)} ${this.#parseFormatting(
+ newContent,
+ 'blackBright'
+ )}`
+ );
+ }
+
+ /**
* Logs information. Highlight information by surrounding it in `<<>>`.
* @param header The header displayed before the content, displayed in cyan.
* @param content The content to log, highlights displayed in bright blue.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param sendChannel Should this also be logged to discord? Defaults to `false`.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
- public static async info(header: string, content: any, sendChannel = true, depth = 0) {
+ public static async info(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
if (!client.config.logging.info) return;
const newContent = this.#inspectContent(content, depth, true);
console.info(
@@ -145,10 +202,10 @@ export class BushLogger {
* Logs warnings. Highlight information by surrounding it in `<<>>`.
* @param header The header displayed before the content, displayed in yellow.
* @param content The content to log, highlights displayed in bright yellow.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param sendChannel Should this also be logged to discord? Defaults to `false`.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
- public static async warn(header: string, content: any, sendChannel = true, depth = 0) {
+ public static async warn(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
const newContent = this.#inspectContent(content, depth, true);
console.warn(
`${chalk.bgYellow(this.#getTimeStamp())} ${chalk.yellow(`[${header}]`)} ${this.#parseFormatting(
@@ -169,10 +226,10 @@ export class BushLogger {
* Logs errors. Highlight information by surrounding it in `<<>>`.
* @param header The header displayed before the content, displayed in bright red.
* @param content The content to log, highlights displayed in bright red.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param sendChannel Should this also be logged to discord? Defaults to `false`.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
- public static async error(header: string, content: any, sendChannel = true, depth = 0) {
+ public static async error(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
const newContent = this.#inspectContent(content, depth, true);
console.error(
`${chalk.bgRedBright(this.#getTimeStamp())} ${chalk.redBright(`[${header}]`)} ${this.#parseFormatting(
@@ -193,10 +250,10 @@ export class BushLogger {
* Logs successes. Highlight information by surrounding it in `<<>>`.
* @param header The header displayed before the content, displayed in green.
* @param content The content to log, highlights displayed in bright green.
- * @param sendChannel Should this also be logged to discord? Defaults to false.
- * @param depth The depth the content will inspected. Defaults to 0.
+ * @param sendChannel Should this also be logged to discord? Defaults to `false`.
+ * @param depth The depth the content will inspected. Defaults to `0`.
*/
- public static async success(header: string, content: any, sendChannel = true, depth = 0) {
+ public static async success(header: string, content: any, sendChannel = true, depth = 0): Promise<void> {
const newContent = this.#inspectContent(content, depth, true);
console.log(
`${chalk.bgGreen(this.#getTimeStamp())} ${chalk.greenBright(`[${header}]`)} ${this.#parseFormatting(
@@ -211,6 +268,23 @@ export class BushLogger {
.setTimestamp();
await this.channelLog({ embeds: [embed] }).catch(() => {});
}
+
+ /**
+ * Asserts a condition. If the condition is false, an error is thrown.
+ * @param type The type of assertion.
+ * @param actual The value to test.
+ * @param expected The expected value.
+ * @param message The error to throw if the assertion fails.
+ */
+ public static assert<T>(type: AssertTypeEqual, actual: unknown, expected: T, message: Error): asserts actual is T;
+ public static assert<T>(type: AssertTypeNotEqual, actual: unknown, expected: T, message: Error): void;
+ public static assert(type: AssertType, actual: unknown, expected: unknown, message: Error): void {
+ assert[type](actual, expected, message);
+ }
}
+export type AssertTypeEqual = 'deepEqual' | 'deepStrictEqual' | 'equal' | 'strictEqual';
+export type AssertTypeNotEqual = 'notDeepEqual' | 'notDeepStrictEqual' | 'notEqual' | 'notStrictEqual';
+export type AssertType = AssertTypeEqual | AssertTypeNotEqual;
+
/** @typedef {PartialTextBasedChannelFields} vscodeDontDeleteMyImportTy */
diff --git a/src/listeners/client/dcjsDebug.ts b/src/listeners/client/dcjsDebug.ts
new file mode 100644
index 0000000..52406c1
--- /dev/null
+++ b/src/listeners/client/dcjsDebug.ts
@@ -0,0 +1,15 @@
+import { BushListener, type BushClientEvents } from '#lib';
+
+export default class DiscordJsDebugListener extends BushListener {
+ public constructor() {
+ super('discordJsDebug', {
+ emitter: 'client',
+ event: 'debug',
+ category: 'client'
+ });
+ }
+
+ public override async exec(...[message]: BushClientEvents['debug']): Promise<void> {
+ void client.console.superVerbose('dc.js-debug', message);
+ }
+}
diff --git a/src/listeners/client/dcjsError.ts b/src/listeners/client/dcjsError.ts
new file mode 100644
index 0000000..4908720
--- /dev/null
+++ b/src/listeners/client/dcjsError.ts
@@ -0,0 +1,15 @@
+import { BushListener, type BushClientEvents } from '#lib';
+
+export default class DiscordJsErrorListener extends BushListener {
+ public constructor() {
+ super('discordJsError', {
+ emitter: 'client',
+ event: 'error',
+ category: 'client'
+ });
+ }
+
+ public override async exec(...[error]: BushClientEvents['error']): Promise<void> {
+ void client.console.superVerbose('dc.js-error', error);
+ }
+}
diff --git a/src/listeners/client/dcjsWarn.ts b/src/listeners/client/dcjsWarn.ts
new file mode 100644
index 0000000..cc2b30f
--- /dev/null
+++ b/src/listeners/client/dcjsWarn.ts
@@ -0,0 +1,15 @@
+import { BushListener, type BushClientEvents } from '#lib';
+
+export default class DiscordJsWarnListener extends BushListener {
+ public constructor() {
+ super('discordJsWarn', {
+ emitter: 'client',
+ event: 'warn',
+ category: 'client'
+ });
+ }
+
+ public override async exec(...[message]: BushClientEvents['warn']): Promise<void> {
+ void client.console.superVerbose('dc.js-warn', message);
+ }
+}
diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts
index c005574..f26ff7f 100644
--- a/src/listeners/commands/commandError.ts
+++ b/src/listeners/commands/commandError.ts
@@ -1,7 +1,7 @@
import { type BushCommandHandlerEvents } from '#lib';
import { Severity } from '@sentry/types';
-import { Command, type AkairoMessage, type GuildTextBasedChannels } from 'discord-akairo';
-import { Formatters, MessageEmbed, type DMChannel, type Message } from 'discord.js';
+import { Command, type AkairoMessage } from 'discord-akairo';
+import { Formatters, GuildTextBasedChannel, MessageEmbed, type DMChannel, type Message } from 'discord.js';
import { BushListener } from '../../lib/extensions/discord-akairo/BushListener.js';
export default class CommandErrorListener extends BushListener {
@@ -24,7 +24,7 @@ export default class CommandErrorListener extends BushListener {
const channel =
message.channel?.type === 'DM'
? (message.channel as DMChannel)!.recipient.tag
- : (message.channel as GuildTextBasedChannels)!.name;
+ : (message.channel as GuildTextBasedChannel)!.name;
const command = _command ?? message.util?.parsed?.command;
client.sentry.captureException(error, {
@@ -38,7 +38,7 @@ export default class CommandErrorListener extends BushListener {
'channel.id':
message.channel!.type === 'DM'
? (message.channel as DMChannel)!.recipient.id
- : (message.channel as GuildTextBasedChannels)!.id,
+ : (message.channel as GuildTextBasedChannel)!.id,
'channel.name': channel,
'guild.id': message.guild?.id,
'guild.name': message.guild?.name,
diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts
index afc689d..76974c8 100644
--- a/src/listeners/commands/commandStarted.ts
+++ b/src/listeners/commands/commandStarted.ts
@@ -1,7 +1,6 @@
import { BushListener, type BushCommandHandlerEvents } from '#lib';
import { Severity } from '@sentry/types';
-import { type GuildTextBasedChannels } from 'discord-akairo';
-import { type DMChannel } from 'discord.js';
+import { GuildTextBasedChannel, type DMChannel } from 'discord.js';
export default class CommandStartedListener extends BushListener {
public constructor() {
@@ -25,11 +24,11 @@ export default class CommandStartedListener extends BushListener {
'channel.id':
message.channel!.type === 'DM'
? (message.channel as DMChannel)!.recipient.id
- : (message.channel as GuildTextBasedChannels)!.id,
+ : (message.channel as GuildTextBasedChannel)!.id,
'channel.name':
message.channel!.type === 'DM'
? (message.channel as DMChannel)!.recipient.tag
- : (message.channel as GuildTextBasedChannels)!.name,
+ : (message.channel as GuildTextBasedChannel)!.name,
'guild.id': message.guild?.id,
'guild.name': message.guild?.name,
'environment': client.config.environment
diff --git a/src/listeners/message/autoThread.ts b/src/listeners/message/autoThread.ts
index c326cec..6ee19ed 100644
--- a/src/listeners/message/autoThread.ts
+++ b/src/listeners/message/autoThread.ts
@@ -1,6 +1,5 @@
import { BushListener, type BushClientEvents, type BushTextChannel } from '#lib';
-import { type GuildTextBasedChannels } from 'discord-akairo';
-import { MessageEmbed } from 'discord.js';
+import { GuildTextBasedChannel, MessageEmbed } from 'discord.js';
export default class autoThreadListener extends BushListener {
public constructor() {
@@ -56,9 +55,9 @@ export default class autoThreadListener extends BushListener {
.then(() =>
client.console.info(
'supportThread',
- `opened a support thread for <<${message.author.tag}>> in <<${
- (message.channel as GuildTextBasedChannels).name
- }>> in <<${message.guild!.name}>>.`
+ `opened a support thread for <<${message.author.tag}>> in <<${(message.channel as GuildTextBasedChannel).name}>> in <<${
+ message.guild!.name
+ }>>.`
)
);
}
diff --git a/src/listeners/other/warning.ts b/src/listeners/other/warning.ts
index 4dd79fe..57f2a7d 100644
--- a/src/listeners/other/warning.ts
+++ b/src/listeners/other/warning.ts
@@ -11,6 +11,8 @@ export default class WarningListener extends BushListener {
}
public override async exec(error: Error) {
+ if (error.name === 'ExperimentalWarning') return;
+
client.sentry.captureException(error, {
level: Severity.Warning
});
diff --git a/tsconfig.json b/tsconfig.json
index 6b7feca..df315ca 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,9 +23,8 @@
"removeComments": true,
"paths": {
"#lib": ["./src/lib/index.js"]
- },
- "skipLibCheck": true
+ }
},
- "include": ["src/**/*.ts", "lib/**/*.ts", "ecosystem.config.cjs"],
+ "include": ["src/**/*.ts", "src/**/*d.ts", "lib/**/*.ts", "ecosystem.config.cjs"],
"exclude": ["dist", "node_modules"]
}
diff --git a/yarn.lock b/yarn.lock
index c6dfbd9..6b6c0ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14,34 +14,23 @@ __metadata:
languageName: node
linkType: hard
-"@discordjs/builders@npm:^0.9.0":
- version: 0.9.0
- resolution: "@discordjs/builders@npm:0.9.0"
+"@discordjs/builders@npm:^0.10.0":
+ version: 0.10.0
+ resolution: "@discordjs/builders@npm:0.10.0"
dependencies:
"@sindresorhus/is": ^4.2.0
- discord-api-types: ^0.24.0
+ discord-api-types: ^0.25.2
ts-mixer: ^6.0.0
tslib: ^2.3.1
zod: ^3.11.6
- checksum: 75278bd4cb2ba09a83e9d308d357e6550fc55af77d7b95d1749ba351d97df80af840144f14f849aa2910966f26a85fcde2271bc1ff5342bbb8ffabe76dd2640c
- languageName: node
- linkType: hard
-
-"@discordjs/collection@npm:^0.3.2":
- version: 0.3.2
- resolution: "@discordjs/collection@npm:0.3.2"
- checksum: 77def13598fce0f33cdd1e45e0e57b4452a1fa348e30d7d3a3b5a56409def4c4785462828b163aea83d659b93412d5dc18c22c85eff499dfc11c24011e8b18e6
+ checksum: 8195f519f28587dee05d9b975c19357c40623d17dc4ced01db11d24faab16969743c40d7cfb959ccb78ffb525c6d03f4f9c821dd712fef57bdd609b53add8727
languageName: node
linkType: hard
-"@discordjs/form-data@npm:^3.0.1":
- version: 3.0.1
- resolution: "@discordjs/form-data@npm:3.0.1"
- dependencies:
- asynckit: ^0.4.0
- combined-stream: ^1.0.8
- mime-types: ^2.1.12
- checksum: 2b431b1a14f8ac521e1c13567856cef7e61e05aafe2721e03d9e074bb7e5f45c89fc123b126964943edf301ec612d04515dcf068ea9773a2e30a99a844c00603
+"@discordjs/collection@npm:^0.4.0":
+ version: 0.4.0
+ resolution: "@discordjs/collection@npm:0.4.0"
+ checksum: fa8fc4246921f3230eb6c5d6d4dc0caf9dd659fcc903175944edf4fb0a9ed9913fdf164733d3f1e644ef469bc79b0d38a526ee620b92169cb40e79b40b0c716b
languageName: node
linkType: hard
@@ -372,13 +361,13 @@ __metadata:
linkType: hard
"@types/express-serve-static-core@npm:^4.17.18":
- version: 4.17.26
- resolution: "@types/express-serve-static-core@npm:4.17.26"
+ version: 4.17.27
+ resolution: "@types/express-serve-static-core@npm:4.17.27"
dependencies:
"@types/node": "*"
"@types/qs": "*"
"@types/range-parser": "*"
- checksum: 064080c3c21136f9017e108559602ec5989ce90828d6ede6e3c375e5693a72500b3c06206cdc4a59496ae1ad8af1e282223efb3d79907233fc4811a2cf4d4392
+ checksum: fef52b941f903011e31a5886369301d7765580a034cd011a2d3a7dbe6a6edf4f77537710a52e3e2258c6fc59c611f228594c213f984cda767654ab6c5c199e9e
languageName: node
linkType: hard
@@ -473,9 +462,9 @@ __metadata:
linkType: hard
"@types/node@npm:*, @types/node@npm:^17.0.2":
- version: 17.0.2
- resolution: "@types/node@npm:17.0.2"
- checksum: a827d2542ef7adba5c79ba7f85b7c2ba8256d317bd99d77ed7af237cfebae0034dff5c4182e1845e6fbef29ae4c78186c4b4a7dbf236037a04120783aa30ba74
+ version: 17.0.4
+ resolution: "@types/node@npm:17.0.4"
+ checksum: 92e6a25fea2314cd34e81962bd07c8b79b92cae04d84a0336a8c49a2b8aa4c34ff8cb428baeac2022daf597809bd3b7987c624b07a91c4d01b6230f82c293190
languageName: node
linkType: hard
@@ -564,9 +553,9 @@ __metadata:
linkType: hard
"@types/validator@npm:^13.7.0":
- version: 13.7.0
- resolution: "@types/validator@npm:13.7.0"
- checksum: 93b1ea1c24efc851db72f86da8e8cb85c16790ae9c6f8edc351048843224f3a2d3596fb8b207443d64300b88638d079f7da9b91a0d9b4426ef7f5f95189b7e85
+ version: 13.7.1
+ resolution: "@types/validator@npm:13.7.1"
+ checksum: 810649a23bc46928c85d933be73b106bacb651a412c4172e30a70e84c63cada595d391042274b16e1c05b391af3a4f157b360890fbc5462ee347275222d59b64
languageName: node
linkType: hard
@@ -1189,11 +1178,11 @@ __metadata:
linkType: hard
"discord-akairo@npm:@notenoughupdates/discord-akairo@dev":
- version: 9.0.10-dev.1640130180.3e8fb1a
- resolution: "@notenoughupdates/discord-akairo@npm:9.0.10-dev.1640130180.3e8fb1a"
+ version: 9.0.10-dev.1640462480.67bad80
+ resolution: "@notenoughupdates/discord-akairo@npm:9.0.10-dev.1640462480.67bad80"
dependencies:
source-map-support: ^0.5.21
- checksum: b637b5bc90f06f40801ad775ed3d94fe3404bfae7e717877552f7fe1e62cc0f100fbacda31733f6a46dab6d66955eba85e3d6df62f82858b363a1127614e3dd5
+ checksum: cf03a768d33d74c6501aba4462c6c3f727a4e164643a41fca0256dfae706d28a104471c3292c919c9977b5c9e2f1aa6a92c918c8872f375fb4e524b1e593d8b8
languageName: node
linkType: hard
@@ -1204,27 +1193,20 @@ __metadata:
languageName: node
linkType: hard
-"discord-api-types@npm:^0.24.0":
- version: 0.24.0
- resolution: "discord-api-types@npm:0.24.0"
- checksum: b1a17cb3be4ade974193cedb92fb37e51ba8f1832dfe3ec0d188ba41f255f3dab8359c9d618d32469f1e648126258f9d6ed8828dc1cb50b74d9fd9d875f3390a
- languageName: node
- linkType: hard
-
"discord.js@npm:@notenoughupdates/discord.js@dev":
- version: 13.4.0-dev.1640088545.d9880cf
- resolution: "@notenoughupdates/discord.js@npm:13.4.0-dev.1640088545.d9880cf"
+ version: 13.5.0-dev.1640476945.bfe6e5f
+ resolution: "@notenoughupdates/discord.js@npm:13.5.0-dev.1640476945.bfe6e5f"
dependencies:
- "@discordjs/builders": ^0.9.0
- "@discordjs/collection": ^0.3.2
- "@discordjs/form-data": ^3.0.1
+ "@discordjs/builders": ^0.10.0
+ "@discordjs/collection": ^0.4.0
"@sapphire/async-queue": ^1.1.9
"@types/node-fetch": ^2.5.12
"@types/ws": ^8.2.2
discord-api-types: ^0.25.2
+ form-data: ^4.0.0
node-fetch: ^2.6.1
- ws: ^8.3.0
- checksum: 0327997ccc235301ef801a89e167d80ca2e1da40be91a5bf71f88c4a73ec49e1a734f17bb5453215e3404b2af83983d49ca4c310eff13c539e3de563f19c2b81
+ ws: ^8.4.0
+ checksum: 782fcb3f0103bc2228fd0a046cf1bb9d3081ebaf9432aa23f94bde269d7cc36644503672c894d6de0bfca2d4101d43e2cba393e30b1cec55df3dae99917a132b
languageName: node
linkType: hard
@@ -1570,6 +1552,17 @@ __metadata:
languageName: node
linkType: hard
+"form-data@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "form-data@npm:4.0.0"
+ dependencies:
+ asynckit: ^0.4.0
+ combined-stream: ^1.0.8
+ mime-types: ^2.1.12
+ checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
+ languageName: node
+ linkType: hard
+
"fraction.js@npm:^4.1.1":
version: 4.1.2
resolution: "fraction.js@npm:4.1.2"
@@ -1601,9 +1594,9 @@ __metadata:
linkType: hard
"fuse.js@npm:^6.4.6":
- version: 6.4.6
- resolution: "fuse.js@npm:6.4.6"
- checksum: 012dfacdc9a3c065d05d031b4eaaad7dd460f66dca100fcef182ed4e70a6b763b6753071702589d0b28def34b48ad077f01426d4ac1d75ef374564f6baa943fd
+ version: 6.5.3
+ resolution: "fuse.js@npm:6.5.3"
+ checksum: f7c14f4422000e7f7e3515c66f7cefdfc38adec4cf380097f4146a201ea438af60b67dc5849b3c0de0115ce3f9bc5afad6fc6570c08dcfcef5bf6e95eb8e6d6f
languageName: node
linkType: hard
@@ -2091,8 +2084,8 @@ __metadata:
linkType: hard
"mathjs@npm:^10.0.0":
- version: 10.0.0
- resolution: "mathjs@npm:10.0.0"
+ version: 10.0.1
+ resolution: "mathjs@npm:10.0.1"
dependencies:
"@babel/runtime": ^7.16.0
complex.js: ^2.0.15
@@ -2105,7 +2098,7 @@ __metadata:
typed-function: ^2.0.0
bin:
mathjs: bin/cli.js
- checksum: 86f4f45804bd799182d5fe54c93a8cc88f1a03a3ab73a9851bc520981310423ec379421b0fb79e50c0a0c22c64cbcf1dcac0636eeaa1b65577366a1c55f27da8
+ checksum: 0665088f28e420d4025e1f8c7413b3735184816496dd57487c7121eb788a056186a960bb78ec282eda59e860a214af70ff5e54783ab0600acdcd35ab841b1a42
languageName: node
linkType: hard
@@ -3283,7 +3276,7 @@ __metadata:
languageName: node
linkType: hard
-"ws@npm:^8.3.0":
+"ws@npm:^8.4.0":
version: 8.4.0
resolution: "ws@npm:8.4.0"
peerDependencies: