aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/common/ButtonPaginator.ts130
-rw-r--r--src/lib/common/DeleteButton.ts41
-rw-r--r--src/lib/common/util/Format.ts107
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts49
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts13
-rw-r--r--src/lib/extensions/discord.js/BushGuildChannelManager.d.ts123
-rw-r--r--src/lib/extensions/discord.js/BushMessage.ts28
-rw-r--r--src/lib/extensions/discord.js/other.ts159
-rw-r--r--src/listeners/message/blacklistedFile.ts3
9 files changed, 559 insertions, 94 deletions
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index d193b4d..83f4219 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -1,50 +1,55 @@
import { DeleteButton, type BushMessage, type BushSlashMessage } from '#lib';
import { CommandUtil } from 'discord-akairo';
import {
- Constants,
MessageActionRow,
MessageButton,
MessageEmbed,
type MessageComponentInteraction,
type MessageEmbedOptions
} from 'discord.js';
+import { MessageButtonStyles } from 'discord.js/typings/enums';
+/**
+ * Sends multiple embeds with controls to switch between them
+ */
export class ButtonPaginator {
+ /**
+ * The message that triggered the command
+ */
protected message: BushMessage | BushSlashMessage;
+
+ /**
+ * The embeds to paginate
+ */
protected embeds: MessageEmbed[] | MessageEmbedOptions[];
+
+ /**
+ * The optional text to send with the paginator
+ */
protected text: string | null;
+
+ /**
+ * Whether the paginator message gets deleted when the exit button is pressed
+ */
protected deleteOnExit: boolean;
+
+ /**
+ * The current page of the paginator
+ */
protected curPage: number;
+
+ /**
+ * The paginator message
+ */
protected sentMessage: BushMessage | undefined;
/**
- * Sends multiple embeds with controls to switch between them
* @param message The message to respond to
* @param embeds The embeds to switch between
* @param text The text send with the embeds (optional)
* @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
* @param startOn The page to start from (**not** the index)
*/
- public static async send(
- message: BushMessage | BushSlashMessage,
- embeds: MessageEmbed[] | MessageEmbedOptions[],
- text: string | null = null,
- deleteOnExit = true,
- startOn = 1
- ) {
- // no need to paginate if there is only one page
- if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds });
-
- return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
- }
-
- /**
- * The number of pages in the paginator
- */
- protected get numPages(): number {
- return this.embeds.length;
- }
-
protected constructor(
message: BushMessage | BushSlashMessage,
embeds: MessageEmbed[] | MessageEmbedOptions[],
@@ -62,7 +67,7 @@ export class ButtonPaginator {
// add footers
for (let i = 0; i < embeds.length; i++) {
if (embeds[i] instanceof MessageEmbed) {
- (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`);
+ (embeds[i] as MessageEmbed).setFooter({ text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` });
} else {
(embeds[i] as MessageEmbedOptions).footer = {
text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`
@@ -71,6 +76,16 @@ export class ButtonPaginator {
}
}
+ /**
+ * The number of pages in the paginator
+ */
+ protected get numPages(): number {
+ return this.embeds.length;
+ }
+
+ /**
+ * Sends the paginator message
+ */
protected async send() {
this.sentMessage = (await this.message.util.reply({
content: this.text,
@@ -87,6 +102,10 @@ export class ButtonPaginator {
collector.on('end', () => void this.end());
}
+ /**
+ * Handles interactions with the paginator
+ * @param interaction The interaction received
+ */
protected async collect(interaction: MessageComponentInteraction) {
if (interaction.user.id !== this.message.author.id && !client.config.owners.includes(interaction.user.id))
return await interaction?.deferUpdate().catch(() => null);
@@ -94,35 +113,44 @@ export class ButtonPaginator {
switch (interaction.customId) {
case 'paginate_beginning':
this.curPage = 0;
- return this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_back':
this.curPage--;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_stop':
if (this.deleteOnExit) {
await interaction.deferUpdate().catch(() => null);
- return await this.sentMessage!.delete().catch(() => null);
+ await this.sentMessage!.delete().catch(() => null);
+ break;
} else {
- return await interaction
+ await interaction
?.update({
content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`,
embeds: [],
components: []
})
.catch(() => null);
+ break;
}
case 'paginate_next':
this.curPage++;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_end':
this.curPage = this.embeds.length - 1;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
}
}
+ /**
+ * Ends the paginator
+ */
protected async end() {
if (this.sentMessage && !CommandUtil.deletedMessages.has(this.sentMessage.id))
- return await this.sentMessage
+ await this.sentMessage
.edit({
content: this.text,
embeds: [this.embeds[this.curPage]],
@@ -131,8 +159,12 @@ export class ButtonPaginator {
.catch(() => null);
}
+ /**
+ * Edits the paginator message
+ * @param interaction The interaction received
+ */
protected async edit(interaction: MessageComponentInteraction) {
- return interaction
+ await interaction
?.update({
content: this.text,
embeds: [this.embeds[this.curPage]],
@@ -141,40 +173,66 @@ export class ButtonPaginator {
.catch(() => null);
}
+ /**
+ * Generates the pagination row based on the class properties
+ * @param disableAll Whether to disable all buttons
+ * @returns The generated {@link MessageActionRow}
+ */
protected getPaginationRow(disableAll = false): MessageActionRow {
return new MessageActionRow().addComponents(
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_beginning',
emoji: PaginateEmojis.BEGGING,
disabled: disableAll || this.curPage === 0
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_back',
emoji: PaginateEmojis.BACK,
disabled: disableAll || this.curPage === 0
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_stop',
emoji: PaginateEmojis.STOP,
disabled: disableAll
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_next',
emoji: PaginateEmojis.FORWARD,
disabled: disableAll || this.curPage === this.numPages - 1
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_end',
emoji: PaginateEmojis.END,
disabled: disableAll || this.curPage === this.numPages - 1
})
);
}
+
+ /**
+ * Sends multiple embeds with controls to switch between them
+ * @param message The message to respond to
+ * @param embeds The embeds to switch between
+ * @param text The text send with the embeds (optional)
+ * @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
+ * @param startOn The page to start from (**not** the index)
+ */
+ public static async send(
+ message: BushMessage | BushSlashMessage,
+ embeds: MessageEmbed[] | MessageEmbedOptions[],
+ text: string | null = null,
+ deleteOnExit = true,
+ startOn = 1
+ ) {
+ // no need to paginate if there is only one page
+ if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds });
+
+ return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
+ }
}
export const enum PaginateEmojis {
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index e2509a9..b666a4f 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -1,25 +1,34 @@
import { PaginateEmojis, type BushMessage, type BushSlashMessage } from '#lib';
import { CommandUtil } from 'discord-akairo';
-import { Constants, MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
+import { MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
+import { MessageButtonStyles } from 'discord.js/typings/enums';
+/**
+ * Sends a message with a button for the user to delete it.
+ */
export class DeleteButton {
+ /**
+ * Options for sending the message
+ */
protected messageOptions: MessageOptions;
- protected message: BushMessage | BushSlashMessage;
/**
- * Sends a message with a button for the user to delete it.
- * @param message - The message to respond to
- * @param options - The send message options
+ * The message that triggered the command
*/
- static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) {
- return new DeleteButton(message, options).send();
- }
+ protected message: BushMessage | BushSlashMessage;
+ /**
+ * @param message The message to respond to
+ * @param options The send message options
+ */
protected constructor(message: BushMessage | BushSlashMessage, options: MessageOptions) {
this.message = message;
this.messageOptions = options;
}
+ /**
+ * Sends a message with a button for the user to delete it.
+ */
protected async send() {
this.updateComponents();
@@ -43,11 +52,16 @@ export class DeleteButton {
});
}
+ /**
+ * Generates the components for the message
+ * @param edit Whether or not the message is being edited
+ * @param disable Whether or not to disable the buttons
+ */
protected updateComponents(edit = false, disable = false): void {
this.messageOptions.components = [
new MessageActionRow().addComponents(
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate__stop',
emoji: PaginateEmojis.STOP,
disabled: disable
@@ -58,4 +72,13 @@ export class DeleteButton {
this.messageOptions.reply = undefined;
}
}
+
+ /**
+ * Sends a message with a button for the user to delete it.
+ * @param message The message to respond to
+ * @param options The send message options
+ */
+ public static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) {
+ return new DeleteButton(message, options).send();
+ }
}
diff --git a/src/lib/common/util/Format.ts b/src/lib/common/util/Format.ts
new file mode 100644
index 0000000..6cb6edc
--- /dev/null
+++ b/src/lib/common/util/Format.ts
@@ -0,0 +1,107 @@
+import { type CodeBlockLang } from '#lib';
+import { EscapeMarkdownOptions, Formatters, Util } from 'discord.js';
+
+/**
+ * Formats and escapes content for formatting
+ */
+export class Format {
+ /**
+ * Wraps the content inside a codeblock with no language.
+ * @param content The content to wrap.
+ */
+ public static codeBlock(content: string): string;
+
+ /**
+ * Wraps the content inside a codeblock with the specified language.
+ * @param language The language for the codeblock.
+ * @param content The content to wrap.
+ */
+ public static codeBlock(language: CodeBlockLang, content: string): string;
+ public static codeBlock(languageOrContent: string, content?: string): string {
+ return typeof content === 'undefined'
+ ? Formatters.codeBlock(Util.escapeCodeBlock(`${languageOrContent}`))
+ : Formatters.codeBlock(`${languageOrContent}`, Util.escapeCodeBlock(`${content}`));
+ }
+
+ /**
+ * Wraps the content inside \`backticks\`, which formats it as inline code.
+ * @param content The content to wrap.
+ */
+ public static inlineCode(content: string): string {
+ return Formatters.inlineCode(Util.escapeInlineCode(`${content}`));
+ }
+
+ /**
+ * Formats the content into italic text.
+ * @param content The content to wrap.
+ */
+ public static italic(content: string): string {
+ return Formatters.italic(Util.escapeItalic(`${content}`));
+ }
+
+ /**
+ * Formats the content into bold text.
+ * @param content The content to wrap.
+ */
+ public static bold(content: string): string {
+ return Formatters.bold(Util.escapeBold(`${content}`));
+ }
+
+ /**
+ * Formats the content into underscored text.
+ * @param content The content to wrap.
+ */
+ public static underscore(content: string): string {
+ return Formatters.underscore(Util.escapeUnderline(`${content}`));
+ }
+
+ /**
+ * Formats the content into strike-through text.
+ * @param content The content to wrap.
+ */
+ public static strikethrough(content: string): string {
+ return Formatters.strikethrough(Util.escapeStrikethrough(`${content}`));
+ }
+
+ /**
+ * Wraps the content inside spoiler (hidden text).
+ * @param content The content to wrap.
+ */
+ public static spoiler(content: string): string {
+ return Formatters.spoiler(Util.escapeSpoiler(`${content}`));
+ }
+
+ /**
+ * Escapes any Discord-flavour markdown in a string.
+ * @param text Content to escape
+ * @param options Options for escaping the markdown
+ */
+ public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string {
+ return Util.escapeMarkdown(`${text}`, options);
+ }
+
+ /**
+ * Formats input: makes it bold and escapes any other markdown
+ * @param text The input
+ */
+ public static input(text: string): string {
+ return this.bold(this.escapeMarkdown(this.sanitizeWtlAndControl(`${text}`)));
+ }
+
+ /**
+ * Formats input for logs: makes it highlighted
+ * @param text The input
+ */
+ public static inputLog(text: string): string {
+ return `<<${this.sanitizeWtlAndControl(`${text}`)}>>`;
+ }
+
+ /**
+ * Removes all characters in a string that are either control characters or change the direction of text etc.
+ * @param str The string you would like sanitized
+ */
+ public static sanitizeWtlAndControl(str: string) {
+ // eslint-disable-next-line no-control-regex
+ return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
+ }
+}
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index 7321c17..706b52a 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -11,33 +11,30 @@ import {
snowflake
} from '#args';
import type {
- BushApplicationCommand,
BushBaseGuildEmojiManager,
BushChannelManager,
BushClientEvents,
BushClientUser,
BushGuildManager,
- BushReactionEmoji,
- BushStageChannel,
BushUserManager,
+ BushUserResolvable,
Config
} from '#lib';
-import { patch, PatchedElements } from '@notenoughupdates/events-intercept';
+import { patch, type 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 Collection,
+ type Awaitable,
+ type If,
type InteractionReplyOptions,
type Message,
type MessageEditOptions,
type MessageOptions,
type MessagePayload,
- type PartialDMChannel,
type ReplyMessageOptions,
type Snowflake,
type WebhookEditMessageOptions
@@ -93,42 +90,6 @@ export type BushEditMessageType = string | MessageEditOptions | MessagePayload;
export type BushSlashSendMessageType = string | MessagePayload | InteractionReplyOptions;
export type BushSlashEditMessageType = string | MessagePayload | WebhookEditMessageOptions;
export type BushSendMessageType = string | MessagePayload | MessageOptions;
-export type BushThreadMemberResolvable = BushThreadMember | BushUserResolvable;
-export type BushUserResolvable = BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember;
-export type BushGuildMemberResolvable = BushGuildMember | BushUserResolvable;
-export type BushRoleResolvable = BushRole | Snowflake;
-export type BushMessageResolvable = Message | BushMessage | Snowflake;
-export type BushEmojiResolvable = Snowflake | BushGuildEmoji | BushReactionEmoji;
-export type BushEmojiIdentifierResolvable = string | BushEmojiResolvable;
-export type BushThreadChannelResolvable = BushThreadChannel | Snowflake;
-export type BushApplicationCommandResolvable = BushApplicationCommand | Snowflake;
-export type BushGuildTextChannelResolvable = BushTextChannel | BushNewsChannel | Snowflake;
-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;
-}
const rl = readline.createInterface({
input: process.stdin,
@@ -136,8 +97,6 @@ const rl = readline.createInterface({
terminal: false
});
-type If<T extends boolean, A, B = null> = T extends true ? A : T extends false ? B : A | B;
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 0d0a0a8..c37a55f 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -511,6 +511,11 @@ export interface BushCommand extends Command {
* @param args - Evaluated arguments.
*/
exec<R, A>(message: BushMessage, args: A): R;
+ /**
+ * Executes the command.
+ * @param message - Message that triggered the command.
+ * @param args - Evaluated arguments.
+ */
exec<R, A>(message: BushMessage | BushSlashMessage, args: A): R;
}
@@ -523,5 +528,9 @@ type SlashOptionKeys =
| keyof AkairoApplicationCommandNumericOptionData
| keyof AkairoApplicationCommandSubCommandData;
-export type ArgType<T extends keyof BaseBushArgumentType> = NonNullable<BaseBushArgumentType[T]>;
-export type OptionalArgType<T extends keyof BaseBushArgumentType> = BaseBushArgumentType[T];
+interface PseudoArguments extends BaseBushArgumentType {
+ boolean: boolean;
+}
+
+export type ArgType<T extends keyof PseudoArguments> = NonNullable<PseudoArguments[T]>;
+export type OptionalArgType<T extends keyof PseudoArguments> = PseudoArguments[T];
diff --git a/src/lib/extensions/discord.js/BushGuildChannelManager.d.ts b/src/lib/extensions/discord.js/BushGuildChannelManager.d.ts
new file mode 100644
index 0000000..3b07145
--- /dev/null
+++ b/src/lib/extensions/discord.js/BushGuildChannelManager.d.ts
@@ -0,0 +1,123 @@
+import type {
+ BushFetchedThreads,
+ BushGuild,
+ BushMappedGuildChannelTypes,
+ BushNonThreadGuildBasedChannel,
+ BushStoreChannel
+} from '#lib';
+import {
+ CachedManager,
+ type BaseFetchOptions,
+ type ChannelPosition,
+ type Collection,
+ type GuildBasedChannel,
+ type GuildChannelCreateOptions,
+ type GuildChannelManager,
+ type GuildChannelResolvable,
+ type GuildChannelTypes,
+ type Snowflake
+} from 'discord.js';
+import type { RawGuildChannelData } from 'discord.js/typings/rawDataTypes';
+
+/**
+ * Manages API methods for GuildChannels and stores their cache.
+ */
+export class BushGuildChannelManager
+ extends CachedManager<Snowflake, GuildBasedChannel, GuildChannelResolvable>
+ implements GuildChannelManager
+{
+ public constructor(guild: BushGuild, iterable?: Iterable<RawGuildChannelData>);
+
+ /**
+ * The number of channels in this managers cache excluding thread channels
+ * that do not count towards a guild's maximum channels restriction.
+ */
+ public readonly channelCountWithoutThreads: number;
+
+ /**
+ * The guild this Manager belongs to
+ */
+ public guild: BushGuild;
+
+ /**
+ * Creates a new channel in the guild.
+ * @param name The name of the new channel
+ * @param options Options for creating the new channel
+ * @example
+ * // Create a new text channel
+ * guild.channels.create('new-general', { reason: 'Needed a cool new channel' })
+ * .then(console.log)
+ * .catch(console.error);
+ * @example
+ * // Create a new channel with permission overwrites
+ * guild.channels.create('new-voice', {
+ * type: 'GUILD_VOICE',
+ * permissionOverwrites: [
+ * {
+ * id: message.author.id,
+ * deny: [Permissions.FLAGS.VIEW_CHANNEL],
+ * },
+ * ],
+ * })
+ * @deprecated 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
+ public create(name: string, options: GuildChannelCreateOptions & { type: 'GUILD_STORE' }): Promise<BushStoreChannel>;
+
+ /**
+ * Creates a new channel in the guild.
+ * @param name The name of the new channel
+ * @param options Options for creating the new channel
+ */
+ public create<T extends GuildChannelTypes>(
+ name: string,
+ options: GuildChannelCreateOptions & { type: T }
+ ): Promise<BushMappedGuildChannelTypes[T]>;
+
+ /**
+ * Creates a new channel in the guild.
+ * @param name The name of the new channel
+ * @param options Options for creating the new channel
+ */
+ public create(name: string, options: GuildChannelCreateOptions): Promise<BushNonThreadGuildBasedChannel>;
+
+ /**
+ * Obtains one or more guild channels from Discord, or the channel cache if they're already available.
+ * @param id The channel's id
+ * @param options Additional options for this fetch
+ * @example
+ * // Fetch all channels from the guild (excluding threads)
+ * message.guild.channels.fetch()
+ * .then(channels => console.log(`There are ${channels.size} channels.`))
+ * .catch(console.error);
+ * @example
+ * // Fetch a single channel
+ * message.guild.channels.fetch('222197033908436994')
+ * .then(channel => console.log(`The channel name is: ${channel.name}`))
+ * .catch(console.error);
+ */
+ public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<BushNonThreadGuildBasedChannel | null>;
+ public fetch(id?: undefined, options?: BaseFetchOptions): Promise<Collection<Snowflake, BushNonThreadGuildBasedChannel>>;
+
+ /**
+ * Batch-updates the guild's channels' positions.
+ * <info>Only one channel's parent can be changed at a time</info>
+ * @param channelPositions Channel positions to update
+ * @example
+ * guild.channels.setPositions([{ channel: channelId, position: newChannelIndex }])
+ * .then(guild => console.log(`Updated channel positions for ${guild}`))
+ * .catch(console.error);
+ */
+ public setPositions(channelPositions: readonly ChannelPosition[]): Promise<BushGuild>;
+
+ /**
+ * Obtains all active thread channels in the guild from Discord
+ * @param cache Whether to cache the fetched data
+ * @example
+ * // Fetch all threads from the guild
+ * message.guild.channels.fetchActiveThreads()
+ * .then(fetched => console.log(`There are ${fetched.threads.size} threads.`))
+ * .catch(console.error);
+ */
+ public fetchActiveThreads(cache?: boolean): Promise<BushFetchedThreads>;
+}
diff --git a/src/lib/extensions/discord.js/BushMessage.ts b/src/lib/extensions/discord.js/BushMessage.ts
index b442196..16c57a2 100644
--- a/src/lib/extensions/discord.js/BushMessage.ts
+++ b/src/lib/extensions/discord.js/BushMessage.ts
@@ -4,10 +4,22 @@ import type {
BushGuild,
BushGuildMember,
BushGuildTextBasedChannel,
+ BushMessageReaction,
BushTextBasedChannel,
+ BushThreadChannel,
BushUser
} from '#lib';
-import { Message, type If, type Partialize } from 'discord.js';
+import {
+ Message,
+ type EmojiIdentifierResolvable,
+ type If,
+ type MessageActionRowComponent,
+ type MessageEditOptions,
+ type MessagePayload,
+ type Partialize,
+ type ReplyMessageOptions,
+ type StartThreadOptions
+} from 'discord.js';
import type { RawMessageData } from 'discord.js/typings/rawDataTypes';
export type PartialBushMessage = Partialize<
@@ -33,5 +45,19 @@ export class BushMessage<Cached extends boolean = boolean> extends Message<Cache
}
export interface BushMessage<Cached extends boolean = boolean> extends Message<Cached> {
+ delete(): Promise<BushMessage>;
+ edit(content: string | MessageEditOptions | MessagePayload): Promise<BushMessage>;
+ equals(message: BushMessage, rawData: unknown): boolean;
+ fetchReference(): Promise<BushMessage>;
+ crosspost(): Promise<BushMessage>;
fetch(force?: boolean): Promise<BushMessage>;
+ pin(): Promise<BushMessage>;
+ react(emoji: EmojiIdentifierResolvable): Promise<BushMessageReaction>;
+ removeAttachments(): Promise<BushMessage>;
+ reply(options: string | MessagePayload | ReplyMessageOptions): Promise<BushMessage>;
+ resolveComponent(customId: string): MessageActionRowComponent | null;
+ startThread(options: StartThreadOptions): Promise<BushThreadChannel>;
+ suppressEmbeds(suppress?: boolean): Promise<BushMessage>;
+ unpin(): Promise<BushMessage>;
+ inGuild(): this is BushMessage<true> & this;
}
diff --git a/src/lib/extensions/discord.js/other.ts b/src/lib/extensions/discord.js/other.ts
new file mode 100644
index 0000000..f81e01c
--- /dev/null
+++ b/src/lib/extensions/discord.js/other.ts
@@ -0,0 +1,159 @@
+import type {
+ BushApplicationCommand,
+ BushCategoryChannel,
+ BushDMChannel,
+ BushGuild,
+ BushGuildEmoji,
+ BushGuildMember,
+ BushMessage,
+ BushNewsChannel,
+ BushReactionEmoji,
+ BushRole,
+ BushStageChannel,
+ BushStoreChannel,
+ BushTextChannel,
+ BushThreadChannel,
+ BushThreadMember,
+ BushUser,
+ BushVoiceChannel
+} from '#lib';
+import type { Collection, EnumValueMapped, Message, PartialDMChannel, Snowflake } from 'discord.js';
+import type { ChannelTypes } from 'discord.js/typings/enums';
+
+/**
+ * Data that resolves to give a ThreadMember object.
+ */
+export type BushThreadMemberResolvable = BushThreadMember | BushUserResolvable;
+
+/**
+ * Data that resolves to give a User object.
+ */
+export type BushUserResolvable = BushUser | Snowflake | BushMessage | BushGuildMember | BushThreadMember;
+
+/**
+ * Data that resolves to give a GuildMember object.
+ */
+export type BushGuildMemberResolvable = BushGuildMember | BushUserResolvable;
+
+/**
+ * Data that can be resolved to a Role object.
+ */
+export type BushRoleResolvable = BushRole | Snowflake;
+
+/**
+ * Data that can be resolved to a Message object.
+ */
+export type BushMessageResolvable = Message | BushMessage | Snowflake;
+
+/**
+ * Data that can be resolved into a GuildEmoji object.
+ */
+export type BushEmojiResolvable = Snowflake | BushGuildEmoji | BushReactionEmoji;
+
+/**
+ * Data that can be resolved to give an emoji identifier. This can be:
+ * * The unicode representation of an emoji
+ * * The `<a:name:id>`, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji
+ * * An EmojiResolvable
+ */
+export type BushEmojiIdentifierResolvable = string | BushEmojiResolvable;
+
+/**
+ * Data that can be resolved to a Thread Channel object.
+ */
+export type BushThreadChannelResolvable = BushThreadChannel | Snowflake;
+
+/**
+ * Data that resolves to give an ApplicationCommand object.
+ */
+export type BushApplicationCommandResolvable = BushApplicationCommand | Snowflake;
+
+/**
+ * Data that can be resolved to a GuildTextChannel object.
+ */
+export type BushGuildTextChannelResolvable = BushTextChannel | BushNewsChannel | Snowflake;
+
+/**
+ * Data that can be resolved to give a Channel object.
+ */
+export type BushChannelResolvable = BushAnyChannel | Snowflake;
+
+/**
+ * Data that can be resolved to give a Guild Channel object.
+ */
+export type BushGuildChannelResolvable = Snowflake | BushGuildBasedChannel;
+
+export type BushAnyChannel =
+ | BushCategoryChannel
+ | BushDMChannel
+ | PartialDMChannel
+ | BushNewsChannel
+ | BushStageChannel
+ // eslint-disable-next-line deprecation/deprecation
+ | BushStoreChannel
+ | BushTextChannel
+ | BushThreadChannel
+ | BushVoiceChannel;
+
+/**
+ * The channels that are text-based.
+ */
+export type BushTextBasedChannel = PartialDMChannel | BushThreadChannel | BushDMChannel | BushNewsChannel | BushTextChannel;
+
+/**
+ * The types of channels that are text-based.
+ */
+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>;
+
+/**
+ * Data that can be resolved to a Text Channel object.
+ */
+export type BushTextChannelResolvable = Snowflake | BushTextChannel;
+
+/**
+ * Data that can be resolved to a GuildVoiceChannel object.
+ */
+export type BushGuildVoiceChannelResolvable = BushVoiceBasedChannel | Snowflake;
+
+export type BushMappedChannelCategoryTypes = EnumValueMapped<
+ typeof ChannelTypes,
+ {
+ GUILD_NEWS: BushNewsChannel;
+ GUILD_VOICE: BushVoiceChannel;
+ GUILD_TEXT: BushTextChannel;
+ // eslint-disable-next-line deprecation/deprecation
+ GUILD_STORE: BushStoreChannel;
+ GUILD_STAGE_VOICE: BushStageChannel;
+ }
+>;
+
+export type BushMappedGuildChannelTypes = EnumValueMapped<
+ typeof ChannelTypes,
+ {
+ GUILD_CATEGORY: BushCategoryChannel;
+ }
+> &
+ BushMappedChannelCategoryTypes;
+
+/**
+ * The data returned from a thread fetch that returns multiple threads.
+ */
+export interface BushFetchedThreads {
+ /**
+ * The threads that were fetched, with any members returned
+ */
+ threads: Collection<Snowflake, BushThreadChannel>;
+
+ /**
+ * Whether there are potentially additional threads that require a subsequent call
+ */
+ hasMore?: boolean;
+}
diff --git a/src/listeners/message/blacklistedFile.ts b/src/listeners/message/blacklistedFile.ts
index 26e1719..5b01218 100644
--- a/src/listeners/message/blacklistedFile.ts
+++ b/src/listeners/message/blacklistedFile.ts
@@ -56,7 +56,7 @@ export default class BlacklistedFileListener extends BushListener {
}
];
- constructor() {
+ public constructor() {
super('blacklistedFile', {
emitter: 'client',
event: 'messageCreate',
@@ -66,6 +66,7 @@ export default class BlacklistedFileListener extends BushListener {
public override async exec(...[message]: BushClientEvents['messageCreate']) {
if (!message.guild || !(await message.guild.hasFeature('blacklistedFile'))) return;
+ // eslint-disable-next-line deprecation/deprecation
const embedAttachments = message.embeds.filter((e) => ['image', 'video', 'gifv'].includes(e.type));
const foundEmojis = [...message.content.matchAll(/<(?<animated>a?):\w+:(?<id>\d+)>/g)];
if (message.attachments.size + embedAttachments.length + foundEmojis.length < 1) return;