Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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_",
Expand Down Expand Up @@ -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"
}
Expand Down
22 changes: 14 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 32 additions & 8 deletions src/client/StelliaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ import {
} from "@managers/index.js";
import {
type ClientEnvironment,
type CustomEmojis,
type GuildConfiguration,
type GuildsConfiguration,
type Manager,
type Managers
} from "@typescript/index.js";
import { logger } from "@utils/index.js";

export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready> {
export class StelliaClient<
Ready extends boolean = boolean,
EmojiEnum extends Record<string, string> = Record<string, string>,
TConfig extends GuildConfiguration = GuildConfiguration,
TGuildsConfig extends GuildsConfiguration = GuildsConfiguration
> extends Client<Ready> {
private utils: StelliaUtils;
public readonly managers: Managers = {};
public readonly environment?: ClientEnvironment;
public customEmojis: CustomEmojis<EmojiEnum> = {} as CustomEmojis<EmojiEnum>;
public readonly emojiEnum?: EmojiEnum;

private constructor(clientOptions: ClientOptions, stelliaOptions?: StelliaOptions) {
super(clientOptions);
Expand All @@ -34,6 +42,10 @@ export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready
this.environment = stelliaOptions.environment;
}

if (stelliaOptions?.emojis) {
this.emojiEnum = stelliaOptions.emojis as EmojiEnum;
}

process.on("unhandledRejection", (error: Error) => {
logger.errorWithInformation("Unhandled promise rejection", error);
});
Expand All @@ -43,8 +55,15 @@ export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready
});
}

public static async create(clientOptions: ClientOptions, stelliaOptions?: StelliaOptions): Promise<StelliaClient> {
const client = new StelliaClient(clientOptions, stelliaOptions);
public static async create<
EmojiEnum extends Record<string, string> = Record<string, string>,
TConfig extends GuildConfiguration = GuildConfiguration,
TGuildsConfig extends GuildsConfiguration = GuildsConfiguration
>(
clientOptions: ClientOptions,
stelliaOptions?: StelliaOptions<EmojiEnum>
): Promise<StelliaClient<boolean, EmojiEnum, TConfig, TGuildsConfig>> {
const client = new StelliaClient<boolean, EmojiEnum, TConfig, TGuildsConfig>(clientOptions, stelliaOptions);
await client.initializeAsyncFields(stelliaOptions);

return client;
Expand All @@ -68,7 +87,11 @@ export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready
await this.utils.initializeCommands();
};

public getGuildsConfiguration = async <CustomGuildsConfiguration extends GuildsConfiguration>(): Promise<CustomGuildsConfiguration> => {
public initializeCustomEmojis = async (): Promise<void> => {
await this.utils.initializeCustomEmojis();
};

public getGuildsConfiguration = async (): Promise<TGuildsConfig> => {
const chosenEnvironment = process.argv.find((arg) => arg.startsWith("--config"))?.split("=")[1];
if (!chosenEnvironment) {
throw new Error("Environment not provided");
Expand Down Expand Up @@ -100,8 +123,8 @@ export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready
});
};

public getGuildConfiguration = <CustomGuildConfiguration extends GuildConfiguration>(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<void> => {
Expand Down Expand Up @@ -157,7 +180,7 @@ export class StelliaClient<Ready extends boolean = boolean> extends Client<Ready
};
}

interface StelliaOptions {
interface StelliaOptions<EmojiEnum extends Record<string, string> = Record<string, string>> {
managers?: {
autoCompletes?: ManagerOptions;
buttons?: ManagerOptions;
Expand All @@ -168,4 +191,5 @@ interface StelliaOptions {
modals?: ManagerOptions;
};
environment?: ClientEnvironment;
}
emojis?: EmojiEnum;
}
44 changes: 42 additions & 2 deletions src/client/StelliaUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EmojiEnum extends Record<string, string> = Record<string, string>> {
public readonly client: StelliaClient;
private readonly interactionHandlers: Map<InteractionType, (interaction: Interaction<"cached">) => Promise<void>>;
private guildsConfiguration: GuildsConfiguration;
Expand Down Expand Up @@ -75,6 +81,40 @@ export class StelliaUtils {
}
};

public initializeCustomEmojis = async (): Promise<void> => {
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<EmojiEnum>;
const typedEmojiEnum = emojiEnum as Record<keyof EmojiEnum, string>;

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[emojiName] = emoji;
}

this.client.customEmojis = customEmojis;
logger.success("Custom emojis resolved successfully");
} catch (error: unknown) {
logger.errorWithInformation("Error while resolving custom emojis", error);
}
};

public getGuildConfiguration = <CustomGuildConfiguration extends GuildConfiguration>(guildId: string): CustomGuildConfiguration | null => {
if (!this.client.environment?.areGuildsConfigurationEnabled || !this.guildsConfiguration) {
return null;
Expand Down
42 changes: 5 additions & 37 deletions src/managers/AutoCompleteManager.ts
Original file line number Diff line number Diff line change
@@ -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 AutoCompleteStructure } from "@structures/index.js";
import { type InteractionCustomId, type StructureCustomId } from "@typescript/index.js";
import { logger, requiredFiles } from "@utils/index.js";

export class AutoCompleteManager extends BaseManager<AutoCompleteStructure> {
private autoCompletes: Collection<StructureCustomId, AutoCompleteStructure> = new Collection();

export class AutoCompleteManager extends SimpleManager<AutoCompleteStructure> {
private constructor(client: StelliaClient, directoryPath: string) {
super(client, directoryPath);
super(client, directoryPath, "auto completes");
}

public static async create(client: StelliaClient, directory: string): Promise<AutoCompleteManager> {
const manager = new AutoCompleteManager(client, directory);
public static async create(client: StelliaClient, directoryPath: string): Promise<AutoCompleteManager> {
const manager = new AutoCompleteManager(client, directoryPath);
await manager.loadData();

return manager;
}

public async loadData(): Promise<void> {
this.autoCompletes = await requiredFiles<AutoCompleteStructure>(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<StructureCustomId, AutoCompleteStructure> {
return this.autoCompletes;
}
}
42 changes: 5 additions & 37 deletions src/managers/ButtonManager.ts
Original file line number Diff line number Diff line change
@@ -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<ButtonStructure> {
public buttons: Collection<StructureCustomId, ButtonStructure> = new Collection();

export class ButtonManager extends SimpleManager<ButtonStructure> {
private constructor(client: StelliaClient, directoryPath: string) {
super(client, directoryPath);
super(client, directoryPath, "buttons");
}

public static async create(client: StelliaClient, directory: string): Promise<ButtonManager> {
const manager = new ButtonManager(client, directory);
public static async create(client: StelliaClient, directoryPath: string): Promise<ButtonManager> {
const manager = new ButtonManager(client, directoryPath);
await manager.loadData();

return manager;
}

public async loadData(): Promise<void> {
this.buttons = await requiredFiles<ButtonStructure>(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<StructureCustomId, ButtonStructure> {
return this.buttons;
}
}
Loading
Loading