From 45d3406f65ca15e347abff33e691983b264c5c1d Mon Sep 17 00:00:00 2001 From: Tweenty Date: Sun, 19 Jul 2026 17:25:48 +0200 Subject: [PATCH 1/4] feat: custom emojis --- src/client/StelliaClient.ts | 22 ++++++++++++++----- src/client/StelliaUtils.ts | 44 +++++++++++++++++++++++++++++++++++-- src/typescript/types.ts | 6 ++++- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/client/StelliaClient.ts b/src/client/StelliaClient.ts index 22c582d..41ab780 100644 --- a/src/client/StelliaClient.ts +++ b/src/client/StelliaClient.ts @@ -15,6 +15,7 @@ import { } from "@managers/index.js"; import { type ClientEnvironment, + type CustomEmojis, type GuildConfiguration, type GuildsConfiguration, type Manager, @@ -22,10 +23,12 @@ import { } from "@typescript/index.js"; import { logger } from "@utils/index.js"; -export class StelliaClient extends Client { +export class StelliaClient = Record> extends Client { private utils: StelliaUtils; public readonly managers: Managers = {}; public readonly environment?: ClientEnvironment; + public customEmojis: CustomEmojis = {} as CustomEmojis; + public readonly emojiEnum?: EmojiEnum; private constructor(clientOptions: ClientOptions, stelliaOptions?: StelliaOptions) { super(clientOptions); @@ -34,6 +37,10 @@ export class StelliaClient extends Client { logger.errorWithInformation("Unhandled promise rejection", error); }); @@ -43,8 +50,8 @@ export class StelliaClient extends Client { - const client = new StelliaClient(clientOptions, stelliaOptions); + public static async create = Record>(clientOptions: ClientOptions, stelliaOptions?: StelliaOptions): Promise> { + const client = new StelliaClient(clientOptions, stelliaOptions); await client.initializeAsyncFields(stelliaOptions); return client; @@ -68,6 +75,10 @@ export class StelliaClient extends Client => { + await this.utils.initializeCustomEmojis(); + }; + public getGuildsConfiguration = async (): Promise => { const chosenEnvironment = process.argv.find((arg) => arg.startsWith("--config"))?.split("=")[1]; if (!chosenEnvironment) { @@ -157,7 +168,7 @@ export class StelliaClient extends Client = Record> { managers?: { autoCompletes?: ManagerOptions; buttons?: ManagerOptions; @@ -168,4 +179,5 @@ interface StelliaOptions { modals?: ManagerOptions; }; environment?: ClientEnvironment; -} + emojis?: EmojiEnum; +} \ No newline at end of file diff --git a/src/client/StelliaUtils.ts b/src/client/StelliaUtils.ts index 79a7600..87a890e 100644 --- a/src/client/StelliaUtils.ts +++ b/src/client/StelliaUtils.ts @@ -30,10 +30,16 @@ import { type SelectMenuStructureWithGuildConfiguration, type SelectMenuStructureWithoutGuildConfiguration } from "@structures/index.js"; -import { type GuildConfiguration, type GuildsConfiguration, InteractionType, type StelliaLocale } from "@typescript/index.js"; +import { + type CustomEmojis, + type GuildConfiguration, + type GuildsConfiguration, + InteractionType, + type StelliaLocale +} from "@typescript/index.js"; import { logger } from "@utils/index.js"; -export class StelliaUtils { +export class StelliaUtils = Record> { public readonly client: StelliaClient; private readonly interactionHandlers: Map) => Promise>; private guildsConfiguration: GuildsConfiguration; @@ -75,6 +81,40 @@ export class StelliaUtils { } }; + public initializeCustomEmojis = async (): Promise => { + const emojiEnum = this.client.emojiEnum; + if (!emojiEnum) { + return; + } + + if (!this.client.application) { + logger.warn("Application not available, cannot resolve custom emojis"); + return; + } + + try { + const applicationEmojis = await this.client.application.emojis.fetch(); + const customEmojis = {} as CustomEmojis; + const typedEmojiEnum = emojiEnum as Record; + + for (const key of Object.keys(emojiEnum) as (keyof EmojiEnum)[]) { + const emojiName = typedEmojiEnum[key]; + const emoji = applicationEmojis.find((appEmoji) => appEmoji.name === emojiName); + if (!emoji) { + logger.warn(`Custom emoji "${emojiName}" not found on this application`); + continue; + } + + customEmojis[key] = emoji; + } + + this.client.customEmojis = customEmojis; + logger.success("Custom emojis resolved successfully"); + } catch (error: unknown) { + logger.errorWithInformation("Error while resolving custom emojis", error); + } + }; + public getGuildConfiguration = (guildId: string): CustomGuildConfiguration | null => { if (!this.client.environment?.areGuildsConfigurationEnabled || !this.guildsConfiguration) { return null; diff --git a/src/typescript/types.ts b/src/typescript/types.ts index 7772649..253e170 100644 --- a/src/typescript/types.ts +++ b/src/typescript/types.ts @@ -1,4 +1,4 @@ -import { type Snowflake } from "discord.js"; +import { type ApplicationEmoji, type Snowflake } from "discord.js"; import { type AutoCompleteManager, type ButtonManager, @@ -67,3 +67,7 @@ export interface GuildConfiguration { guild: BaseGuildConfiguration; } export type GuildConfigurationType = GuildConfiguration | null; + +export type CustomEmojis> = { + [Key in keyof EmojiEnum]: ApplicationEmoji; +}; \ No newline at end of file From 38bae6d6ef81917911d170b8c5832637f83b886c Mon Sep 17 00:00:00 2001 From: Tweenty Date: Sun, 19 Jul 2026 18:48:03 +0200 Subject: [PATCH 2/4] refactor: managers + events typing --- src/client/StelliaClient.ts | 24 ++++++-- src/client/StelliaUtils.ts | 2 +- src/managers/AutoCompleteManager.ts | 42 ++----------- src/managers/ButtonManager.ts | 42 ++----------- src/managers/CommandManager.ts | 42 ++----------- src/managers/ContextMenuManager.ts | 42 ++----------- src/managers/EventManager.ts | 95 +++++++++++++++++------------ src/managers/ModalManager.ts | 42 ++----------- src/managers/SelectMenuManager.ts | 42 ++----------- src/managers/SimpleManager.ts | 39 ++++++++++++ src/managers/index.ts | 1 + src/structures/Event.ts | 56 +++++++++++------ src/structures/Interaction.ts | 2 +- src/utils/ComponentUtils.ts | 18 +++--- 14 files changed, 193 insertions(+), 296 deletions(-) create mode 100644 src/managers/SimpleManager.ts diff --git a/src/client/StelliaClient.ts b/src/client/StelliaClient.ts index 41ab780..2a8a01d 100644 --- a/src/client/StelliaClient.ts +++ b/src/client/StelliaClient.ts @@ -23,7 +23,12 @@ import { } from "@typescript/index.js"; import { logger } from "@utils/index.js"; -export class StelliaClient = Record> extends Client { +export class StelliaClient< + Ready extends boolean = boolean, + EmojiEnum extends Record = Record, + TConfig extends GuildConfiguration = GuildConfiguration, + TGuildsConfig extends GuildsConfiguration = GuildsConfiguration +> extends Client { private utils: StelliaUtils; public readonly managers: Managers = {}; public readonly environment?: ClientEnvironment; @@ -50,8 +55,15 @@ export class StelliaClient = Record>(clientOptions: ClientOptions, stelliaOptions?: StelliaOptions): Promise> { - const client = new StelliaClient(clientOptions, stelliaOptions); + public static async create< + EmojiEnum extends Record = Record, + TConfig extends GuildConfiguration = GuildConfiguration, + TGuildsConfig extends GuildsConfiguration = GuildsConfiguration + >( + clientOptions: ClientOptions, + stelliaOptions?: StelliaOptions + ): Promise> { + const client = new StelliaClient(clientOptions, stelliaOptions); await client.initializeAsyncFields(stelliaOptions); return client; @@ -79,7 +91,7 @@ export class StelliaClient(): Promise => { + public getGuildsConfiguration = async (): Promise => { const chosenEnvironment = process.argv.find((arg) => arg.startsWith("--config"))?.split("=")[1]; if (!chosenEnvironment) { throw new Error("Environment not provided"); @@ -111,8 +123,8 @@ export class StelliaClient(guildId: string): CustomGuildConfiguration | null => { - return this.utils.getGuildConfiguration(guildId); + public getGuildConfiguration = (guildId: string): TConfig | null => { + return this.utils.getGuildConfiguration(guildId) as TConfig | null; }; public handleInteraction = async (interaction: Interaction<"cached">): Promise => { diff --git a/src/client/StelliaUtils.ts b/src/client/StelliaUtils.ts index 87a890e..6fe3d6c 100644 --- a/src/client/StelliaUtils.ts +++ b/src/client/StelliaUtils.ts @@ -105,7 +105,7 @@ export class StelliaUtils = Record { - private autoCompletes: Collection = new Collection(); +export class AutoCompleteManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "auto completes"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new AutoCompleteManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new AutoCompleteManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.autoCompletes = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.autoCompletes.size} auto completes`); - } - - public getByCustomId(id: InteractionCustomId): AutoCompleteStructure | null { - return this.autoCompletes.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): AutoCompleteStructure | null { - let autoComplete: AutoCompleteStructure | null = null; - for (const [customId, action] of this.autoCompletes.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - autoComplete = action; - break; - } - } - - return autoComplete; - } - - public getAll(): Collection { - return this.autoCompletes; - } } diff --git a/src/managers/ButtonManager.ts b/src/managers/ButtonManager.ts index 36ef766..a4641ac 100644 --- a/src/managers/ButtonManager.ts +++ b/src/managers/ButtonManager.ts @@ -1,48 +1,16 @@ -import { Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { BaseManager } from "@managers/index.js"; +import { SimpleManager } from "@managers/index.js"; import { type ButtonStructure } from "@structures/index.js"; -import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; -import { logger, requiredFiles } from "@utils/index.js"; - -export class ButtonManager extends BaseManager { - public buttons: Collection = new Collection(); +export class ButtonManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "buttons"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new ButtonManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new ButtonManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.buttons = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.buttons.size} buttons`); - } - - public getByCustomId(id: InteractionCustomId): ButtonStructure | null { - return this.buttons.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): ButtonStructure | null { - let button: ButtonStructure | null = null; - for (const [customId, action] of this.buttons.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - button = action; - break; - } - } - - return button; - } - - public getAll(): Collection { - return this.buttons; - } } diff --git a/src/managers/CommandManager.ts b/src/managers/CommandManager.ts index 19dff24..bcc0be0 100644 --- a/src/managers/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -1,48 +1,16 @@ -import { Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { BaseManager } from "@managers/index.js"; +import { SimpleManager } from "@managers/index.js"; import { type CommandStructure } from "@structures/index.js"; -import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; -import { logger, requiredFiles } from "@utils/index.js"; - -export class CommandManager extends BaseManager { - private commands: Collection = new Collection(); +export class CommandManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "commands"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new CommandManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new CommandManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.commands = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.commands.size} commands`); - } - - public getByCustomId(id: InteractionCustomId): CommandStructure | null { - return this.commands.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): CommandStructure | null { - let command: CommandStructure | null = null; - for (const [customId, action] of this.commands.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - command = action; - break; - } - } - - return command; - } - - public getAll(): Collection { - return this.commands; - } } diff --git a/src/managers/ContextMenuManager.ts b/src/managers/ContextMenuManager.ts index a7a80fe..16d3961 100644 --- a/src/managers/ContextMenuManager.ts +++ b/src/managers/ContextMenuManager.ts @@ -1,48 +1,16 @@ -import { Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { BaseManager } from "@managers/index.js"; +import { SimpleManager } from "@managers/index.js"; import { type ContextMenuStructure } from "@structures/index.js"; -import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; -import { logger, requiredFiles } from "@utils/index.js"; - -export class ContextMenuManager extends BaseManager { - private contextMenus: Collection = new Collection(); +export class ContextMenuManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "context menus"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new ContextMenuManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new ContextMenuManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.contextMenus = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.contextMenus.size} context menus`); - } - - public getByCustomId(id: InteractionCustomId): ContextMenuStructure | null { - return this.contextMenus.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): ContextMenuStructure | null { - let contextMenu: ContextMenuStructure | null = null; - for (const [customId, action] of this.contextMenus.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - contextMenu = action; - break; - } - } - - return contextMenu; - } - - public getAll(): Collection { - return this.contextMenus; - } } diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 2b9778f..570123d 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -1,14 +1,6 @@ -import { Collection } from "discord.js"; +import { type Awaitable, Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; import { BaseManager } from "@managers/index.js"; -import { - type ClientEventsArgs, - type EventStructure, - type EventStructureWithAllGuildsConfiguration, - type EventStructureWithConfiguration, - type EventStructureWithGuildConfiguration, - type EventStructureWithoutGuildConfiguration -} from "@structures/index.js"; import { type GuildConfigurationType, type GuildsConfiguration, @@ -16,6 +8,9 @@ import { type StructureCustomId } from "@typescript/index.js"; import { logger, requiredFiles } from "@utils/index.js"; +import { type EventStructure, type EventStructureWithGuildConfiguration } from "structures/index.js"; + +type UnsafeEventExecute = (client: StelliaClient, ...args: unknown[]) => Awaitable; export class EventManager extends BaseManager { private events: Collection = new Collection(); @@ -70,7 +65,7 @@ export class EventManager extends BaseManager { private async loadEventWithGuildConfiguration(eventStructure: EventStructure) { const { name, once } = eventStructure.data; - const event = eventStructure as EventStructureWithGuildConfiguration; + const event = eventStructure as EventStructureWithGuildConfiguration; if (once) { this.client.once(name, (...args) => this.eventHandler(event, ...args)); @@ -79,52 +74,74 @@ export class EventManager extends BaseManager { } } - private readonly eventHandler = (event: EventStructureWithConfiguration, ...args: ClientEventsArgs) => { - const mainArgument = args[0]; - const guildConfiguration = this.getGuildConfiguration(mainArgument); - if (guildConfiguration) { - const eventStructure = event as EventStructureWithGuildConfiguration; - return eventStructure.execute(this.client, guildConfiguration, ...args); - } + private readonly eventHandler = async (event: EventStructureWithGuildConfiguration, ...args: unknown[]): Promise => { + try { + const execute = event.execute as UnsafeEventExecute; + const mainArgument = args[0]; + const guildConfiguration = this.getGuildConfiguration(mainArgument); - const eventStructure = event as EventStructureWithAllGuildsConfiguration; - return eventStructure.execute(this.client, this.guildsConfiguration, ...args); + if (guildConfiguration) { + await execute(this.client, guildConfiguration, ...args); + return; + } + + await execute(this.client, this.guildsConfiguration, ...args); + } catch (error: unknown) { + logger.errorWithInformation(`Error while executing event "${String(event.data.name)}"`, error); + } }; private async loadEventWithoutGuildConfiguration(eventStructure: EventStructure): Promise { const { name, once } = eventStructure.data; - const event = eventStructure as EventStructureWithoutGuildConfiguration; + const execute = eventStructure.execute as UnsafeEventExecute; + + const handler = async (...args: unknown[]): Promise => { + try { + await execute(this.client, ...args); + } catch (error: unknown) { + logger.errorWithInformation(`Error while executing event "${String(name)}"`, error); + } + }; if (once) { - this.client.once(name, (...args) => event.execute(this.client, ...args)); + this.client.once(name, handler); } else { - this.client.on(name, (...args) => event.execute(this.client, ...args)); + this.client.on(name, handler); } } - private getGuildConfiguration(mainArgument: ClientEventsArgs[0]): GuildConfigurationType | undefined { - if (mainArgument && typeof mainArgument === "object") { - if ("guildId" in mainArgument && mainArgument.guildId) { - return this.client.getGuildConfiguration(mainArgument.guildId); - } - if ("guild" in mainArgument && mainArgument.guild) { - return this.client.getGuildConfiguration(mainArgument.guild.id); - } - if ( - "message" in mainArgument && - mainArgument.message && - typeof mainArgument.message === "object" && - "guild" in mainArgument.message && - mainArgument.message.guild && - "id" in mainArgument.message.guild - ) { - return this.client.getGuildConfiguration(mainArgument.message.guild.id); + private getGuildConfiguration(mainArgument: unknown): GuildConfigurationType | undefined { + const guildId = this.extractGuildId(mainArgument); + return guildId ? this.client.getGuildConfiguration(guildId) : undefined; + } + + private extractGuildId(mainArgument: unknown): string | undefined { + if (!mainArgument || typeof mainArgument !== "object") { + return undefined; + } + + if ("guildId" in mainArgument && typeof mainArgument.guildId === "string") { + return mainArgument.guildId; + } + + if ("guild" in mainArgument && this.isObjectWithStringId(mainArgument.guild)) { + return mainArgument.guild.id; + } + + if ("message" in mainArgument && typeof mainArgument.message === "object" && mainArgument.message !== null) { + const message = mainArgument.message as Record; + if ("guild" in message && this.isObjectWithStringId(message.guild)) { + return message.guild.id; } } return undefined; } + private isObjectWithStringId(value: unknown): value is { id: string } { + return typeof value === "object" && value !== null && "id" in value && typeof (value as Record).id === "string"; + } + private async initializeGuildsConfiguration(): Promise { if (this.client.environment?.areGuildsConfigurationEnabled) { try { diff --git a/src/managers/ModalManager.ts b/src/managers/ModalManager.ts index c4008cc..d560ddf 100644 --- a/src/managers/ModalManager.ts +++ b/src/managers/ModalManager.ts @@ -1,48 +1,16 @@ -import { Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { BaseManager } from "@managers/index.js"; +import { SimpleManager } from "@managers/index.js"; import { type ModalStructure } from "@structures/index.js"; -import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; -import { logger, requiredFiles } from "@utils/index.js"; - -export class ModalManager extends BaseManager { - private modals: Collection = new Collection(); +export class ModalManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "modals"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new ModalManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new ModalManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.modals = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.modals.size} modals`); - } - - public getByCustomId(id: InteractionCustomId): ModalStructure | null { - return this.modals.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): ModalStructure | null { - let modal: ModalStructure | null = null; - for (const [customId, action] of this.modals.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - modal = action; - break; - } - } - - return modal; - } - - public getAll(): Collection { - return this.modals; - } } diff --git a/src/managers/SelectMenuManager.ts b/src/managers/SelectMenuManager.ts index efc7a75..ad9e22b 100644 --- a/src/managers/SelectMenuManager.ts +++ b/src/managers/SelectMenuManager.ts @@ -1,48 +1,16 @@ -import { Collection } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { BaseManager } from "@managers/index.js"; +import { SimpleManager } from "@managers/index.js"; import { type SelectMenuStructure } from "@structures/index.js"; -import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; -import { logger, requiredFiles } from "@utils/index.js"; - -export class SelectMenuManager extends BaseManager { - private selectMenus: Collection = new Collection(); +export class SelectMenuManager extends SimpleManager { private constructor(client: StelliaClient, directoryPath: string) { - super(client, directoryPath); + super(client, directoryPath, "select menus"); } - public static async create(client: StelliaClient, directory: string): Promise { - const manager = new SelectMenuManager(client, directory); + public static async create(client: StelliaClient, directoryPath: string): Promise { + const manager = new SelectMenuManager(client, directoryPath); await manager.loadData(); return manager; } - - public async loadData(): Promise { - this.selectMenus = await requiredFiles(this.directoryPath); - this.setManagerLoaded(); - - logger.info(`Loaded ${this.selectMenus.size} select menus`); - } - - public getByCustomId(id: InteractionCustomId): SelectMenuStructure | null { - return this.selectMenus.get(id) ?? null; - } - - public getByRegex(id: InteractionCustomId): SelectMenuStructure | null { - let selectMenu: SelectMenuStructure | null = null; - for (const [customId, action] of this.selectMenus.entries()) { - if (customId instanceof RegExp && customId.test(id)) { - selectMenu = action; - break; - } - } - - return selectMenu; - } - - public getAll(): Collection { - return this.selectMenus; - } } diff --git a/src/managers/SimpleManager.ts b/src/managers/SimpleManager.ts new file mode 100644 index 0000000..942e4d1 --- /dev/null +++ b/src/managers/SimpleManager.ts @@ -0,0 +1,39 @@ +import { Collection } from "discord.js"; +import { type StelliaClient } from "@client/index.js"; +import { BaseManager } from "@managers/index.js"; +import { type AnyInteractionStructure } from "@structures/index.js"; +import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js"; +import { logger, requiredFiles } from "@utils/index.js"; + +export abstract class SimpleManager extends BaseManager { + protected items: Collection = new Collection(); + + protected constructor(client: StelliaClient, directoryPath: string, private readonly label: string) { + super(client, directoryPath); + } + + public async loadData(): Promise { + this.items = await requiredFiles(this.directoryPath); + this.setManagerLoaded(); + + logger.info(`Loaded ${this.items.size} ${this.label}`); + } + + public getByCustomId(id: InteractionCustomId): TStructure | null { + return this.items.get(id) ?? null; + } + + public getByRegex(id: InteractionCustomId): TStructure | null { + for (const [customId, structure] of this.items.entries()) { + if (customId instanceof RegExp && customId.test(id)) { + return structure; + } + } + + return null; + } + + public getAll(): Collection { + return this.items; + } +} diff --git a/src/managers/index.ts b/src/managers/index.ts index d339c11..7931cf8 100644 --- a/src/managers/index.ts +++ b/src/managers/index.ts @@ -1,4 +1,5 @@ export * from "@managers/BaseManager.js"; +export * from "@managers/SimpleManager.js"; export * from "@managers/AutoCompleteManager.js"; export * from "@managers/ButtonManager.js"; export * from "@managers/CommandManager.js"; diff --git a/src/structures/Event.ts b/src/structures/Event.ts index 460a816..dfa14fc 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -2,27 +2,47 @@ import { type Awaitable, type ClientEvents } from "discord.js"; import { type StelliaClient } from "@client/index.js"; import { type GuildConfigurationType, type GuildsConfiguration } from "@typescript/index.js"; -export interface EventStructureWithGuildConfiguration extends EventInteractionStructure { - execute(client: StelliaClient, guildConfiguration: GuildConfigurationType, ...args: ClientEventsArgs): Awaitable; +export type EventKeys = keyof ClientEvents; + +export type EventExecute = (client: StelliaClient, ...args: [...ExtraArgs, ...ClientEvents[K]]) => Awaitable; + +export interface EventDataStructure { + name: K; + once?: boolean; } -export interface EventStructureWithAllGuildsConfiguration extends EventInteractionStructure { - execute(client: StelliaClient, guildsConfiguration: GuildsConfiguration, ...args: ClientEventsArgs): Awaitable; + +export interface EventStructureWithoutGuildConfiguration { + data: EventDataStructure; + execute: EventExecute; } -export interface EventStructureWithoutGuildConfiguration extends EventInteractionStructure { - execute(client: StelliaClient, ...args: ClientEventsArgs): Awaitable; + +export interface EventStructureWithGuildConfiguration { + data: EventDataStructure; + execute: EventExecute; +} + +export interface EventStructureWithAllGuildsConfiguration { + data: EventDataStructure; + execute: EventExecute; } -export type EventStructureWithConfiguration = EventStructureWithGuildConfiguration | EventStructureWithAllGuildsConfiguration; -export type EventStructure = - | EventStructureWithGuildConfiguration - | EventStructureWithAllGuildsConfiguration - | EventStructureWithoutGuildConfiguration; -interface EventInteractionStructure { - data: EventDataStructure; +export function defineEvent(event: EventStructureWithoutGuildConfiguration) { + return event; } -interface EventDataStructure { - name: EventKeys; - once: boolean; + +export function createGuildEventFactory() { + return function (event: EventStructureWithGuildConfiguration) { + return event; + }; } -export type ClientEventsArgs = ClientEvents[keyof ClientEvents]; -export type EventKeys = keyof ClientEvents; + +export function createAllGuildsEventFactory() { + return function (event: EventStructureWithAllGuildsConfiguration) { + return event; + }; +} + +export type EventStructure = + | EventStructureWithGuildConfiguration + | EventStructureWithAllGuildsConfiguration + | EventStructureWithoutGuildConfiguration; \ No newline at end of file diff --git a/src/structures/Interaction.ts b/src/structures/Interaction.ts index d0fcf80..c635c59 100644 --- a/src/structures/Interaction.ts +++ b/src/structures/Interaction.ts @@ -12,8 +12,8 @@ import { type UserContextMenuCommandInteraction } from "discord.js"; import { type StelliaClient } from "@client/index.js"; -import { type EventStructure } from "@structures/index.js"; import { type GuildConfigurationType, type StelliaLocale } from "@typescript/index.js"; +import { type EventStructure } from "structures/Event.js"; export interface AutoCompleteStructureWithGuildConfiguration extends Omit { data: Omit; diff --git a/src/utils/ComponentUtils.ts b/src/utils/ComponentUtils.ts index 21e7084..e716d22 100644 --- a/src/utils/ComponentUtils.ts +++ b/src/utils/ComponentUtils.ts @@ -1,17 +1,17 @@ import { type APIContainerComponent, type APIEmbed, embedLength, TextDisplayComponent } from "discord.js"; export namespace ComponentUtils { - export const getEmbedContentLength = (embed: APIEmbed) => { + export const getEmbedContentLength = (embed: APIEmbed): number => { return embedLength(embed); }; - export const getContainerContentLength = (container: APIContainerComponent) => { - return container.components.map((component) => { - if (component instanceof TextDisplayComponent) { - return component.content.length; - } + export const getContainerContentLength = (container: APIContainerComponent): number => { + return container.components.reduce((total, component) => { + if (component instanceof TextDisplayComponent) { + return total + component.content.length; + } - return 0; - }).reduce((a, b) => a + b); + return total; + }, 0); }; -} \ No newline at end of file +} From f0cb5e2da4d9e9a2037e3822762dabed55944768 Mon Sep 17 00:00:00 2001 From: Tweenty Date: Sun, 19 Jul 2026 18:50:00 +0200 Subject: [PATCH 3/4] bump: deps --- package.json | 2 +- pnpm-lock.yaml | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 12c6ecb..d26f968 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "globals": "^17.7.0", "jiti": "^2.7.0", "prettier": "^3.9.5", - "tsdown": "^0.22.9", + "tsdown": "^0.22.11", "typescript": "^7.0.2", "typescript-eslint": "^8.64.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39607a7..b210c5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,8 +43,8 @@ importers: specifier: ^3.9.5 version: 3.9.5 tsdown: - specifier: ^0.22.9 - version: 0.22.9(typescript@7.0.2) + specifier: ^0.22.11 + version: 0.22.11(typescript@7.0.2) typescript: specifier: ^7.0.2 version: 7.0.2 @@ -1039,6 +1039,10 @@ packages: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1144,14 +1148,14 @@ packages: ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} - tsdown@0.22.9: - resolution: {integrity: sha512-/0QEjQEhOU1t1YxOIAGzFN7bIssd8P0pBpkOmNLCQi3c5UtrcMF5bvq3f30xHJNW9QCA9aUNcNAorMr2CTd6Lg==} + tsdown@0.22.11: + resolution: {integrity: sha512-BERdQpqAltv3vYGC0pE4n+mRGPIN91T/on+Ud73bYRkbptknelFm27tJgBBUz4RJ5d3VNeVH90HfjBo1Ouj8yQ==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.9 - '@tsdown/exe': 0.22.9 + '@tsdown/css': 0.22.11 + '@tsdown/exe': 0.22.11 '@vitejs/devtools': '*' publint: ^0.3.8 tsx: '*' @@ -2077,6 +2081,8 @@ snapshots: obug@2.1.3: {} + obug@2.1.4: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2170,7 +2176,7 @@ snapshots: ts-mixer@6.0.4: {} - tsdown@0.22.9(typescript@7.0.2): + tsdown@0.22.11(typescript@7.0.2): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -2178,7 +2184,7 @@ snapshots: empathic: 2.0.1 hookable: 6.1.1 import-without-cache: 0.4.0 - obug: 2.1.3 + obug: 2.1.4 picomatch: 4.0.5 rolldown: 1.2.0 rolldown-plugin-dts: 0.27.9(rolldown@1.2.0)(typescript@7.0.2) From 7e3a6b4c2cba09f7182b69df73983df9f6760e74 Mon Sep 17 00:00:00 2001 From: Tweenty Date: Sun, 19 Jul 2026 18:50:15 +0200 Subject: [PATCH 4/4] release: 1.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d26f968..3d99585 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stelliajs/framework", - "version": "1.8.1", + "version": "1.9.0", "type": "module", "description": "A framework for simplifying the creation of discord bots", "author": "Tweenty_",