bakit 2.0.0-alpha.1 → 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/bakit.js ADDED
@@ -0,0 +1,39 @@
1
+ // @ts-check
2
+ import { config as useEnv } from "dotenv";
3
+ import { program } from "commander";
4
+
5
+ program.name("bakit");
6
+
7
+ program.command("dev").action(async () => {
8
+ useEnv({
9
+ path: [".env.local", ".env"],
10
+ quiet: true,
11
+ });
12
+ const { default: nodemon } = await import("nodemon");
13
+
14
+ nodemon({
15
+ script: "src/index.ts",
16
+ exec: `${process.execPath} --import tsx`,
17
+ ext: "ts,js",
18
+ watch: ["src"],
19
+ env: {
20
+ ...process.env,
21
+ FORCE_COLOR: "1",
22
+ NODE_ENV: "development",
23
+ },
24
+ });
25
+
26
+ nodemon.on("start", () => {
27
+ console.log("Starting bakit app...");
28
+ });
29
+
30
+ nodemon.on("restart", () => {
31
+ console.log("Bakit detected changes! Restarting...");
32
+ });
33
+
34
+ nodemon.on("quit", () => {
35
+ process.exit();
36
+ });
37
+ });
38
+
39
+ program.parse();
package/dist/index.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- import { GatewayIntentBits, ClientOptions, Message, Awaitable, ClientEvents, Client } from 'discord.js';
2
- import { z } from 'zod';
1
+ import * as discord_js from 'discord.js';
2
+ import { GatewayIntentBits, ClientOptions, ChatInputCommandInteraction, CacheType, Message, User, MessageCreateOptions, InteractionReplyOptions, Awaitable, Collection, Events, IntentsBitField, Client, ClientEvents } from 'discord.js';
3
+ import z$1, { z } from 'zod';
4
+ import * as jiti from 'jiti';
5
+ import { inspect } from 'node:util';
3
6
 
4
7
  declare const ProjectConfigSchema: z.ZodObject<{
5
8
  intents: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBigInt, z.ZodArray<z.ZodEnum<typeof GatewayIntentBits>>]>>;
6
9
  clientOptions: z.ZodOptional<z.ZodCustom<Omit<ClientOptions, "intents">, Omit<ClientOptions, "intents">>>;
10
+ entryDir: z.ZodDefault<z.ZodString>;
11
+ prefixes: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ token: z.ZodString;
7
13
  }, z.core.$strip>;
8
14
  type ProjectConfigInput = z.input<typeof ProjectConfigSchema>;
9
15
  type ProjectConfig = z.output<typeof ProjectConfigSchema>;
@@ -25,13 +31,340 @@ declare function loadConfig(cwd?: string): Promise<ProjectConfig>;
25
31
  */
26
32
  declare function getConfig(): ProjectConfig;
27
33
 
34
+ declare const EVENT_INTENT_MAPPING: Record<string, number[]>;
35
+
36
+ declare function tokenize(content: string): string[];
37
+ /**
38
+ * Extracts a valid Discord Snowflake (User ID, Channel ID, etc.) from a string.
39
+ * @param input The raw string to parse
40
+ * @returns The extracted ID string, or null if invalid
41
+ */
42
+ declare function extractSnowflakeId(input: string): string | null;
43
+
44
+ declare const $jiti: jiti.Jiti;
45
+
46
+ declare class Context {
47
+ canceled: boolean;
48
+ cancel(): void;
49
+ }
50
+
51
+ type ChatInputContextSendOptions = string | InteractionReplyOptions;
52
+ type MessageContextSendOptions = string | MessageCreateOptions;
53
+ type ContextSendOptions = ChatInputContextSendOptions | MessageContextSendOptions;
54
+ declare abstract class BaseCommandContext<Cached extends boolean, InGuild extends boolean> extends Context {
55
+ source: ChatInputCommandInteraction<Cached extends true ? "cached" : CacheType> | Message<InGuild>;
56
+ constructor(source: ChatInputCommandInteraction<Cached extends true ? "cached" : CacheType> | Message<InGuild>);
57
+ get client(): BakitClient<true>;
58
+ get channel(): discord_js.CacheTypeReducer<Cached extends true ? "cached" : CacheType, discord_js.GuildTextBasedChannel | null, discord_js.GuildTextBasedChannel | null, discord_js.GuildTextBasedChannel | null, discord_js.TextBasedChannel | null> | discord_js.If<InGuild, discord_js.GuildTextBasedChannel, discord_js.TextBasedChannel>;
59
+ get channelId(): string;
60
+ get guild(): discord_js.CacheTypeReducer<Cached extends true ? "cached" : CacheType, discord_js.Guild, null, discord_js.Guild | null, discord_js.Guild | null> | discord_js.If<InGuild, discord_js.Guild, null>;
61
+ get guildId(): discord_js.CacheTypeReducer<Cached extends true ? "cached" : CacheType, string, string, string, string | null> | discord_js.If<InGuild, string, null>;
62
+ get member(): discord_js.GuildMember | discord_js.CacheTypeReducer<Cached extends true ? "cached" : CacheType, discord_js.GuildMember, discord_js.APIInteractionGuildMember, discord_js.GuildMember | discord_js.APIInteractionGuildMember, discord_js.GuildMember | discord_js.APIInteractionGuildMember | null> | null;
63
+ inGuild(): this is CommandContext<Cached, true>;
64
+ inCachedGuild(): this is CommandContext<true, true>;
65
+ get user(): User;
66
+ isChatInput(): this is ChatInputContext;
67
+ isMessage(): this is MessageContext;
68
+ abstract send(options: ContextSendOptions): Promise<Message<InGuild>>;
69
+ }
70
+ declare class ChatInputContext<Cached extends boolean = boolean, InGuild extends boolean = boolean> extends BaseCommandContext<Cached, InGuild> {
71
+ source: ChatInputCommandInteraction<Cached extends true ? "cached" : CacheType>;
72
+ send(options: ContextSendOptions): Promise<Message<InGuild>>;
73
+ }
74
+ declare class MessageContext<Cached extends boolean = boolean, InGuild extends boolean = boolean> extends BaseCommandContext<Cached, InGuild> {
75
+ source: Message<InGuild>;
76
+ send(options: string | MessageCreateOptions): Promise<Message<InGuild>>;
77
+ }
78
+ type CommandContext<Cached extends boolean = boolean, InGuild extends boolean = boolean> = ChatInputContext<Cached, InGuild> | MessageContext<Cached, InGuild>;
79
+
80
+ declare enum HookState {
81
+ Pre = "PRE",
82
+ Main = "MAIN",
83
+ Post = "POST",
84
+ Error = "ERROR"
85
+ }
86
+ declare enum HookOrder {
87
+ First = 0,
88
+ Last = 1
89
+ }
90
+ type MainHookCallback<C extends Context, Args extends unknown[]> = (context: C, ...args: Args) => Awaitable<void>;
91
+ type ErrorHookCallback<C extends Context, Args extends unknown[]> = (context: C, error: unknown, ...args: Args) => Awaitable<void>;
92
+ declare class LifecycleManager<C extends Context, Args extends unknown[]> {
93
+ id: string;
94
+ private readonly hooks;
95
+ constructor(id: string);
96
+ getName(name: string): string;
97
+ setHook(name: string, state: HookState.Post, callback: MainHookCallback<C, Args>, order?: HookOrder): this;
98
+ setHook(name: string, state: HookState.Main, callback: MainHookCallback<C, Args>, order?: HookOrder): this;
99
+ setHook(name: string, state: HookState.Pre, callback: MainHookCallback<C, Args>, order?: HookOrder): this;
100
+ setHook(name: string, state: HookState.Error, callback: ErrorHookCallback<C, Args>, order?: HookOrder): this;
101
+ main(callback: MainHookCallback<C, Args>): this;
102
+ pre(callback: MainHookCallback<C, Args>): this;
103
+ post(callback: MainHookCallback<C, Args>): this;
104
+ error(callback: ErrorHookCallback<C, Args>): this;
105
+ execute(context: C, ...args: Args): Promise<void>;
106
+ }
107
+
108
+ declare enum ParamUserType {
109
+ Bot = "bot",
110
+ Normal = "normal",
111
+ Any = "any"
112
+ }
113
+ declare const BaseParamSchema: z.ZodObject<{
114
+ name: z.ZodString;
115
+ description: z.ZodOptional<z.ZodString>;
116
+ required: z.ZodDefault<z.ZodBoolean>;
117
+ }, z.core.$strip>;
118
+ declare const StringParamSchema: z.ZodObject<{
119
+ name: z.ZodString;
120
+ description: z.ZodOptional<z.ZodString>;
121
+ required: z.ZodDefault<z.ZodBoolean>;
122
+ maxLength: z.ZodOptional<z.ZodNumber>;
123
+ minLength: z.ZodOptional<z.ZodNumber>;
124
+ }, z.core.$strip>;
125
+ declare const NumberParamSchema: z.ZodObject<{
126
+ name: z.ZodString;
127
+ description: z.ZodOptional<z.ZodString>;
128
+ required: z.ZodDefault<z.ZodBoolean>;
129
+ maxValue: z.ZodOptional<z.ZodNumber>;
130
+ minValue: z.ZodOptional<z.ZodNumber>;
131
+ }, z.core.$strip>;
132
+ declare const UserParamSchema: z.ZodObject<{
133
+ name: z.ZodString;
134
+ description: z.ZodOptional<z.ZodString>;
135
+ required: z.ZodDefault<z.ZodBoolean>;
136
+ }, z.core.$strip>;
137
+ type BaseParamOptions = z.input<typeof BaseParamSchema>;
138
+ type StringOptions = z.input<typeof StringParamSchema>;
139
+ type NumberOptions = z.input<typeof NumberParamSchema>;
140
+ type UserOptions = z.input<typeof UserParamSchema>;
141
+
142
+ type ParamResolvedOutputType<OutputType, Required extends boolean = true> = Required extends true ? OutputType : OutputType | null;
143
+ declare abstract class BaseParam<Options extends BaseParamOptions, OutputType, Required extends boolean = true> {
144
+ private schema;
145
+ options: Options & {
146
+ required: Required;
147
+ };
148
+ /**
149
+ * **Internal Phantom Type**
150
+ *
151
+ * Used strictly for TypeScript type inference to determine the runtime value
152
+ * of this parameter. This property does not exist at runtime.
153
+ *
154
+ * @internal
155
+ */
156
+ readonly _type: Required extends true ? OutputType : OutputType | null;
157
+ constructor(options: Options, schema: z$1.ZodObject);
158
+ protected setOption(key: keyof Options, value: any): this;
159
+ name(value: string): this;
160
+ description(value: string): this;
161
+ required<V extends boolean>(value: V): BaseParam<Options, OutputType, V>;
162
+ resolve(context: CommandContext, value?: string): Promise<ParamResolvedOutputType<OutputType, Required>>;
163
+ abstract resolveMessage(context: MessageContext, value: string): Awaitable<ParamResolvedOutputType<OutputType, Required>>;
164
+ abstract resolveChatInput(context: ChatInputContext): Awaitable<ParamResolvedOutputType<OutputType, Required>>;
165
+ /**
166
+ * Helper to normalize string inputs into an options object.
167
+ */
168
+ protected static getOptions<Options>(options: Options | string): Options;
169
+ }
170
+ declare class StringParam<Required extends boolean = true> extends BaseParam<StringOptions, string, Required> {
171
+ constructor(options: string | StringOptions);
172
+ required<V extends boolean>(value: V): StringParam<V>;
173
+ resolveMessage(_context: CommandContext, value: string): ParamResolvedOutputType<string, Required>;
174
+ resolveChatInput(context: ChatInputContext): ParamResolvedOutputType<string, Required>;
175
+ /**
176
+ * Sets the minimum allowed length for this string.
177
+ * Pass `null` to remove this constraint.
178
+ */
179
+ min(length: number | null): this;
180
+ /**
181
+ * Sets the maximum allowed length for this string.
182
+ * Pass `null` to remove this constraint.
183
+ */
184
+ max(length: number | null): this;
185
+ }
186
+ declare class NumberParam<Required extends boolean = true> extends BaseParam<NumberOptions, number, Required> {
187
+ constructor(options: string | NumberOptions);
188
+ required<V extends boolean>(value: V): NumberParam<V>;
189
+ resolveMessage(_context: CommandContext, value: string): ParamResolvedOutputType<number, Required>;
190
+ resolveChatInput(context: ChatInputContext): ParamResolvedOutputType<number, Required>;
191
+ /**
192
+ * Sets the minimum allowed value for this number.
193
+ * Pass `null` to remove this constraint.
194
+ */
195
+ min(value: number | null): this;
196
+ /**
197
+ * Sets the maximum allowed value for this number.
198
+ * Pass `null` to remove this constraint.
199
+ */
200
+ max(value: number | null): this;
201
+ }
202
+ declare class UserParam<Required extends boolean = true> extends BaseParam<UserOptions, User, Required> {
203
+ constructor(options: string | UserOptions);
204
+ required<V extends boolean>(value: V): UserParam<V>;
205
+ resolveMessage(context: CommandContext, value: string): Promise<ParamResolvedOutputType<User, Required>>;
206
+ resolveChatInput(context: ChatInputContext): ParamResolvedOutputType<User, Required>;
207
+ }
208
+ type AnyParam<Required extends boolean = true> = BaseParam<any, any, Required>;
209
+ /**
210
+ * Helper type to extract the runtime value of a Param instance.
211
+ *
212
+ * @example
213
+ * const p = new StringParam("name").required(false);
214
+ * type T = InferParamValue<typeof p>; // string | null
215
+ */
216
+ type InferParamValue<P extends AnyParam<any>> = P["_type"];
217
+ type InferParamTuple<T extends readonly BaseParam<any, any, any>[]> = {
218
+ [K in keyof T]: T[K] extends AnyParam<any> ? InferParamValue<T[K]> : never;
219
+ };
220
+
221
+ declare function validateParamsOrder(params: readonly AnyParam<boolean>[]): boolean;
222
+ declare const CommandOptionsSchema: z.ZodPipe<z.ZodObject<{
223
+ name: z.ZodReadonly<z.ZodString>;
224
+ description: z.ZodReadonly<z.ZodOptional<z.ZodString>>;
225
+ nsfw: z.ZodReadonly<z.ZodDefault<z.ZodBoolean>>;
226
+ params: z.ZodReadonly<z.ZodDefault<z.ZodArray<z.ZodCustom<BaseParam<{
227
+ name: string;
228
+ description?: string | undefined;
229
+ required?: boolean | undefined;
230
+ }, unknown, boolean>, BaseParam<{
231
+ name: string;
232
+ description?: string | undefined;
233
+ required?: boolean | undefined;
234
+ }, unknown, boolean>>>>>;
235
+ quotes: z.ZodReadonly<z.ZodDefault<z.ZodBoolean>>;
236
+ }, z.core.$strip>, z.ZodTransform<{
237
+ description: string;
238
+ name: string;
239
+ nsfw: boolean;
240
+ params: readonly BaseParam<{
241
+ name: string;
242
+ description?: string | undefined;
243
+ required?: boolean | undefined;
244
+ }, unknown, boolean>[];
245
+ quotes: boolean;
246
+ }, {
247
+ name: string;
248
+ nsfw: boolean;
249
+ params: readonly BaseParam<{
250
+ name: string;
251
+ description?: string | undefined;
252
+ required?: boolean | undefined;
253
+ }, unknown, boolean>[];
254
+ quotes: boolean;
255
+ description?: string | undefined;
256
+ }>>;
257
+ type CommandOptionsInput = z.input<typeof CommandOptionsSchema>;
258
+ type CommandOptions = z.output<typeof CommandOptionsSchema>;
259
+ /**
260
+ * The command entry, used for registering command.
261
+ */
262
+ declare class Command<ParamsList extends readonly AnyParam<any>[] = any[]> extends LifecycleManager<CommandContext, [
263
+ ...args: InferParamTuple<ParamsList>
264
+ ]> {
265
+ options: CommandOptions;
266
+ constructor(options: (Omit<CommandOptionsInput, "params"> & {
267
+ params?: ParamsList;
268
+ }) | string);
269
+ private handleSyntaxError;
270
+ toSlashCommandJSON(): discord_js.RESTPostAPIChatInputApplicationCommandsJSONBody;
271
+ private initSlashCommandOptions;
272
+ private initSlashCommandOption;
273
+ }
274
+ /**
275
+ * Define command entry, usually for modules.
276
+ * @param options The command options.
277
+ * @returns The entry of the command to deploy or register hooks.
278
+ * @example
279
+ * ```ts
280
+ * import { defineCommand } from "bakit";
281
+ *
282
+ * const command = defineCommand({
283
+ * name: "ping",
284
+ * description: "Displays bot's latency.",
285
+ * });
286
+ *
287
+ * command.main(async (context) => {
288
+ * await context.send(`Pong! ${context.client.ws.ping}ms!`);
289
+ * });
290
+ *
291
+ * export default command;
292
+ * ```
293
+ */
294
+ declare function defineCommand<const ParamsList extends readonly AnyParam<any>[] = any[]>(options: (Omit<CommandOptionsInput, "params"> & {
295
+ params?: ParamsList;
296
+ }) | string): Command<ParamsList>;
297
+
298
+ declare class CommandManager extends BaseClientManager {
299
+ commands: Collection<string, Command<any[]>>;
300
+ loadModules(): Promise<Command[]>;
301
+ add(command: Command): void;
302
+ remove(target: string | Command): Command | undefined;
303
+ get(name: string): Command<any[]> | undefined;
304
+ }
305
+
306
+ declare const ListenerOptionsSchema: z$1.ZodObject<{
307
+ name: z$1.ZodEnum<typeof Events>;
308
+ once: z$1.ZodDefault<z$1.ZodBoolean>;
309
+ }, z$1.z.core.$strip>;
310
+ type ListenerOptions<K extends EventKey = EventKey> = Omit<z$1.input<typeof ListenerOptionsSchema>, "name"> & {
311
+ name: K;
312
+ };
313
+ type EventKey = keyof BakitClientEvents;
314
+ declare class Listener<K extends EventKey = EventKey> extends LifecycleManager<Context, [
315
+ ...args: BakitClientEvents[K]
316
+ ]> {
317
+ options: ListenerOptions<K>;
318
+ constructor(options: K | ListenerOptions<K>);
319
+ }
320
+ declare function defineListener<const K extends EventKey = EventKey>(options: K | ListenerOptions<K>): Listener<K>;
321
+
322
+ declare class ListenerManager extends BaseClientManager {
323
+ listeners: Listener[];
324
+ private executors;
325
+ loadModules(): Promise<Listener[]>;
326
+ add(listener: Listener): void;
327
+ remove(target: string | Listener): Listener[];
328
+ getBaseIntents(): IntentsBitField;
329
+ getNeededIntents(): IntentsBitField;
330
+ }
331
+
332
+ declare class ProjectCacheManager {
333
+ private readonly rootDir;
334
+ constructor(root?: string);
335
+ private ensureRoot;
336
+ getHash(data: unknown): string;
337
+ write(path: string, data: unknown): Promise<void>;
338
+ read<T>(path: string): Promise<T | null>;
339
+ clear(): Promise<void>;
340
+ clearSync(): void;
341
+ }
342
+
343
+ declare class Instance {
344
+ client: BakitClient;
345
+ cache: ProjectCacheManager;
346
+ constructor();
347
+ start(): Promise<void>;
348
+ private loadModules;
349
+ private initIntents;
350
+ }
351
+ declare function useApp(): Instance;
352
+
28
353
  type GetPrefixFunction = (message: Message) => Awaitable<string[] | string>;
29
354
  interface BakitClientEvents extends ClientEvents {
30
355
  ready: [BakitClient<true>];
31
356
  clientReady: [BakitClient<true>];
32
357
  }
33
358
  declare class BakitClient<Ready extends boolean = boolean> extends Client<Ready> {
34
- constructor(options: ClientOptions);
359
+ instance: Instance;
360
+ managers: {
361
+ commands: CommandManager;
362
+ listeners: ListenerManager;
363
+ };
364
+ constructor(options: ClientOptions, instance: Instance);
365
+ /**
366
+ * Check if the client is connected to gateway successfully and finished initialization.
367
+ */
35
368
  isReady(): this is BakitClient<true>;
36
369
  on<K extends keyof BakitClientEvents>(event: K, listener: (...args: BakitClientEvents[K]) => void): this;
37
370
  once<K extends keyof BakitClientEvents>(event: K, listener: (...args: BakitClientEvents[K]) => void): this;
@@ -39,6 +372,43 @@ declare class BakitClient<Ready extends boolean = boolean> extends Client<Ready>
39
372
  removeAllListeners(event?: keyof BakitClientEvents): this;
40
373
  removeListener<K extends keyof BakitClientEvents>(event: K, listener: (...args: BakitClientEvents[K]) => void): this;
41
374
  emit<K extends keyof BakitClientEvents>(event: K, ...args: BakitClientEvents[K]): boolean;
375
+ /**
376
+ * Override BakitClient output when using logger for security concern.
377
+ * @returns `BakitClient {}`
378
+ */
379
+ [inspect.custom](): string;
380
+ }
381
+
382
+ declare class BaseClientManager {
383
+ client: BakitClient;
384
+ constructor(client: BakitClient);
385
+ }
386
+
387
+ declare const Params: {
388
+ readonly string: <Required extends boolean = true>(options: string | {
389
+ name: string;
390
+ description?: string | undefined;
391
+ required?: boolean | undefined;
392
+ maxLength?: number | undefined;
393
+ minLength?: number | undefined;
394
+ }) => StringParam<Required>;
395
+ readonly number: <Required extends boolean = true>(options: string | {
396
+ name: string;
397
+ description?: string | undefined;
398
+ required?: boolean | undefined;
399
+ maxValue?: number | undefined;
400
+ minValue?: number | undefined;
401
+ }) => NumberParam<Required>;
402
+ };
403
+
404
+ declare class BakitError extends Error {
405
+ constructor(message: string);
406
+ }
407
+
408
+ declare class ArgumentError extends BakitError {
409
+ target: string;
410
+ reason: string;
411
+ constructor(target: string, reason: string);
42
412
  }
43
413
 
44
- export { BakitClient, type BakitClientEvents, type GetPrefixFunction, type ProjectConfig, type ProjectConfigInput, defineConfig, getConfig, loadConfig };
414
+ export { $jiti, type AnyParam, ArgumentError, BakitClient, type BakitClientEvents, BakitError, BaseClientManager, BaseCommandContext, BaseParam, type BaseParamOptions, BaseParamSchema, ChatInputContext, type ChatInputContextSendOptions, Command, type CommandContext, CommandManager, type CommandOptions, type CommandOptionsInput, CommandOptionsSchema, Context, type ContextSendOptions, EVENT_INTENT_MAPPING, type ErrorHookCallback, type GetPrefixFunction, HookOrder, HookState, type InferParamTuple, type InferParamValue, Instance, LifecycleManager, Listener, ListenerManager, type ListenerOptions, ListenerOptionsSchema, type MainHookCallback, MessageContext, type MessageContextSendOptions, type NumberOptions, NumberParam, NumberParamSchema, type ParamResolvedOutputType, ParamUserType, Params, type ProjectConfig, type ProjectConfigInput, ProjectConfigSchema, type StringOptions, StringParam, StringParamSchema, type UserOptions, UserParam, UserParamSchema, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, loadConfig, tokenize, useApp, validateParamsOrder };
package/dist/index.js CHANGED
@@ -1,12 +1,169 @@
1
- import { GatewayIntentBits, Client } from 'discord.js';
2
- import { z } from 'zod';
3
- import { pathToFileURL } from 'url';
1
+ import { GatewayIntentBits, Events, Client, IntentsBitField, Collection, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
2
+ import z4, { z } from 'zod';
4
3
  import glob from 'tiny-glob';
4
+ import { createJiti } from 'jiti';
5
+ import { inspect } from 'util';
6
+ import { posix, join, dirname } from 'path';
7
+ import { existsSync, mkdirSync, rmSync } from 'fs';
8
+ import { mkdir, writeFile, readFile, rm } from 'fs/promises';
9
+ import { createHash } from 'crypto';
10
+
11
+ // src/config.ts
12
+ var INTENT_GROUPS = {
13
+ [GatewayIntentBits.Guilds]: [
14
+ Events.GuildCreate,
15
+ Events.GuildDelete,
16
+ Events.GuildUpdate,
17
+ Events.GuildUnavailable,
18
+ Events.GuildRoleCreate,
19
+ Events.GuildRoleDelete,
20
+ Events.GuildRoleUpdate,
21
+ Events.ChannelCreate,
22
+ Events.ChannelDelete,
23
+ Events.ChannelUpdate,
24
+ Events.ChannelPinsUpdate,
25
+ Events.ThreadCreate,
26
+ Events.ThreadDelete,
27
+ Events.ThreadUpdate,
28
+ Events.ThreadListSync,
29
+ Events.ThreadMemberUpdate,
30
+ Events.ThreadMembersUpdate,
31
+ Events.StageInstanceCreate,
32
+ Events.StageInstanceUpdate,
33
+ Events.StageInstanceDelete
34
+ ],
35
+ [GatewayIntentBits.GuildMembers]: [
36
+ Events.GuildMemberAdd,
37
+ Events.GuildMemberUpdate,
38
+ Events.GuildMemberRemove,
39
+ Events.ThreadMembersUpdate
40
+ ],
41
+ [GatewayIntentBits.GuildModeration]: [Events.GuildAuditLogEntryCreate, Events.GuildBanAdd, Events.GuildBanRemove],
42
+ [GatewayIntentBits.GuildExpressions]: [
43
+ Events.GuildEmojiCreate,
44
+ Events.GuildEmojiDelete,
45
+ Events.GuildEmojiUpdate,
46
+ Events.GuildStickerCreate,
47
+ Events.GuildStickerDelete,
48
+ Events.GuildStickerUpdate,
49
+ Events.GuildSoundboardSoundCreate,
50
+ Events.GuildSoundboardSoundUpdate,
51
+ Events.GuildSoundboardSoundDelete,
52
+ Events.GuildSoundboardSoundsUpdate
53
+ ],
54
+ [GatewayIntentBits.GuildIntegrations]: [Events.GuildIntegrationsUpdate],
55
+ [GatewayIntentBits.GuildWebhooks]: [Events.WebhooksUpdate],
56
+ [GatewayIntentBits.GuildInvites]: [Events.InviteCreate, Events.InviteDelete],
57
+ [GatewayIntentBits.GuildVoiceStates]: [Events.VoiceStateUpdate],
58
+ [GatewayIntentBits.GuildPresences]: [Events.PresenceUpdate],
59
+ [GatewayIntentBits.GuildMessages]: [
60
+ Events.MessageCreate,
61
+ Events.MessageUpdate,
62
+ Events.MessageDelete,
63
+ Events.MessageBulkDelete
64
+ ],
65
+ [GatewayIntentBits.GuildMessageReactions]: [
66
+ Events.MessageReactionAdd,
67
+ Events.MessageReactionRemove,
68
+ Events.MessageReactionRemoveAll,
69
+ Events.MessageReactionRemoveEmoji
70
+ ],
71
+ [GatewayIntentBits.GuildMessageTyping]: [Events.TypingStart],
72
+ [GatewayIntentBits.DirectMessages]: [
73
+ Events.MessageCreate,
74
+ Events.MessageUpdate,
75
+ Events.MessageDelete,
76
+ Events.ChannelPinsUpdate
77
+ ],
78
+ [GatewayIntentBits.DirectMessageReactions]: [
79
+ Events.MessageReactionAdd,
80
+ Events.MessageReactionRemove,
81
+ Events.MessageReactionRemoveAll,
82
+ Events.MessageReactionRemoveEmoji
83
+ ],
84
+ [GatewayIntentBits.DirectMessageTyping]: [Events.TypingStart],
85
+ [GatewayIntentBits.MessageContent]: [Events.MessageCreate, Events.MessageUpdate],
86
+ [GatewayIntentBits.GuildScheduledEvents]: [
87
+ Events.GuildScheduledEventCreate,
88
+ Events.GuildScheduledEventDelete,
89
+ Events.GuildScheduledEventUpdate,
90
+ Events.GuildScheduledEventUserAdd,
91
+ Events.GuildScheduledEventUserRemove
92
+ ],
93
+ [GatewayIntentBits.AutoModerationConfiguration]: [
94
+ Events.AutoModerationRuleCreate,
95
+ Events.AutoModerationRuleDelete,
96
+ Events.AutoModerationRuleUpdate
97
+ ],
98
+ [GatewayIntentBits.AutoModerationExecution]: [Events.AutoModerationActionExecution],
99
+ [GatewayIntentBits.GuildMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove],
100
+ [GatewayIntentBits.DirectMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove]
101
+ }, EVENT_INTENT_MAPPING = {};
102
+ for (let [intentStr, events] of Object.entries(INTENT_GROUPS)) {
103
+ let intent = Number(intentStr);
104
+ for (let event of events)
105
+ EVENT_INTENT_MAPPING[event] ??= [], EVENT_INTENT_MAPPING[event].includes(intent) || EVENT_INTENT_MAPPING[event].push(intent);
106
+ }
107
+
108
+ // src/utils/string.ts
109
+ function tokenize(content) {
110
+ let args = [], current = "", quoteChar = null, isEscaped = false;
111
+ for (let i = 0; i < content.length; i++) {
112
+ let char = content[i];
113
+ if (char === void 0)
114
+ break;
115
+ if (isEscaped) {
116
+ current += char, isEscaped = false;
117
+ continue;
118
+ }
119
+ if (char === "\\") {
120
+ isEscaped = true;
121
+ continue;
122
+ }
123
+ quoteChar ? char === quoteChar ? quoteChar = null : current += char : char === '"' ? quoteChar = char : /\s/.test(char) ? current.length > 0 && (args.push(current), current = "") : current += char;
124
+ }
125
+ return current.length > 0 && args.push(current), args;
126
+ }
127
+ function extractSnowflakeId(input) {
128
+ if (!input) return null;
129
+ let mentionMatch = /^<@!?(\d{17,20})>$/.exec(input);
130
+ if (mentionMatch?.[1])
131
+ return mentionMatch[1];
132
+ let idMatch = /^(\d{17,20})$/.exec(input);
133
+ return idMatch?.[1] ? idMatch[1] : null;
134
+ }
135
+
136
+ // src/utils/index.ts
137
+ var $jiti = createJiti(import.meta.url);
5
138
 
6
139
  // src/config.ts
7
140
  var ProjectConfigSchema = z.object({
141
+ /**
142
+ * The gateway intents to use for the Discord client.
143
+ *
144
+ * - `auto` — automatically determine the required intents.
145
+ * - bigint — a raw bitfield value representing the combined intents.
146
+ * - array — a list of individual intent flags from `GatewayIntentBits`.
147
+ *
148
+ * @defaultvalue `auto`
149
+ */
8
150
  intents: z.union([z.literal("auto"), z.bigint(), z.array(z.enum(GatewayIntentBits))]).default("auto"),
9
- clientOptions: z.custom().optional()
151
+ /**
152
+ * Optional custom client options for Discord.js (excluding `intents`).
153
+ *
154
+ * These are passed directly to the `Client` constructor when initializing the bot.
155
+ *
156
+ * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
157
+ */
158
+ clientOptions: z.custom().optional(),
159
+ /**
160
+ * The path to the main project source directory.
161
+ *
162
+ * @defaultvalue `src`
163
+ */
164
+ entryDir: z.string().default("src"),
165
+ prefixes: z.array(z.string()).default([]),
166
+ token: z.string()
10
167
  });
11
168
  function defineConfig(config) {
12
169
  return config;
@@ -23,7 +180,7 @@ async function loadConfig(cwd = process.cwd()) {
23
180
  if (!configPath)
24
181
  throw new Error("Missing config file");
25
182
  other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
26
- let configFileURL = pathToFileURL(configPath).toString(), { default: config } = await import(configFileURL);
183
+ let config = await $jiti.import(configPath, { default: true });
27
184
  return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
28
185
  }
29
186
  function getConfig() {
@@ -31,10 +188,516 @@ function getConfig() {
31
188
  throw new Error("Project config is not loaded.");
32
189
  return _config;
33
190
  }
34
- var BakitClient = class extends Client {
191
+
192
+ // src/base/BaseClientManager.ts
193
+ var BaseClientManager = class {
194
+ constructor(client) {
195
+ this.client = client;
196
+ }
197
+ };
198
+
199
+ // src/base/lifecycle/Context.ts
200
+ var Context = class {
201
+ canceled = false;
202
+ cancel() {
203
+ this.canceled = true;
204
+ }
205
+ };
206
+
207
+ // src/base/command/CommandContext.ts
208
+ var BaseCommandContext = class extends Context {
209
+ constructor(source) {
210
+ super();
211
+ this.source = source;
212
+ }
213
+ get client() {
214
+ return this.source.client;
215
+ }
216
+ get channel() {
217
+ return this.source.channel;
218
+ }
219
+ get channelId() {
220
+ return this.source.channelId;
221
+ }
222
+ get guild() {
223
+ return this.source.guild;
224
+ }
225
+ get guildId() {
226
+ return this.source.guildId;
227
+ }
228
+ get member() {
229
+ return this.source.member;
230
+ }
231
+ inGuild() {
232
+ return !!this.guildId;
233
+ }
234
+ inCachedGuild() {
235
+ if (this.isChatInput())
236
+ return this.source.inCachedGuild();
237
+ if (this.isMessage())
238
+ return this.source.inGuild();
239
+ throw new Error("Invalid source");
240
+ }
241
+ get user() {
242
+ if (this.isChatInput())
243
+ return this.source.user;
244
+ if (this.isMessage())
245
+ return this.source.author;
246
+ throw new Error("Invalid source");
247
+ }
248
+ isChatInput() {
249
+ return this.source instanceof ChatInputCommandInteraction;
250
+ }
251
+ isMessage() {
252
+ return this.source instanceof Message;
253
+ }
254
+ }, ChatInputContext = class extends BaseCommandContext {
255
+ async send(options) {
256
+ typeof options == "string" && (options = { content: options });
257
+ let sendOptions = {
258
+ ...options,
259
+ withResponse: true
260
+ };
261
+ return this.source.deferred || this.source.replied ? await this.source.followUp(sendOptions) : (await this.source.reply(sendOptions)).resource?.message;
262
+ }
263
+ }, MessageContext = class extends BaseCommandContext {
264
+ async send(options) {
265
+ let { channel } = this;
266
+ if (!channel?.isSendable())
267
+ throw new Error("Invalid channel or channel is not sendable");
268
+ return await channel.send(options);
269
+ }
270
+ };
271
+ var HookState = /* @__PURE__ */ ((HookState2) => (HookState2.Pre = "PRE", HookState2.Main = "MAIN", HookState2.Post = "POST", HookState2.Error = "ERROR", HookState2))(HookState || {}), HookOrder = /* @__PURE__ */ ((HookOrder2) => (HookOrder2[HookOrder2.First = 0] = "First", HookOrder2[HookOrder2.Last = 1] = "Last", HookOrder2))(HookOrder || {}), LifecycleManager = class {
272
+ constructor(id) {
273
+ this.id = id;
274
+ }
275
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
276
+ hooks = {
277
+ MAIN: new Collection(),
278
+ PRE: new Collection(),
279
+ POST: new Collection(),
280
+ ERROR: new Collection()
281
+ };
282
+ getName(name) {
283
+ return `${this.id}:${name}`;
284
+ }
285
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
286
+ setHook(name, state, callback, order = 1 /* Last */) {
287
+ let currentHooks = this.hooks[state], key = this.getName(name);
288
+ if (currentHooks.has(key) && console.warn(`Overriding duplicate hook '${key}' for state '${state}'`), order === 1 /* Last */)
289
+ currentHooks.set(key, callback);
290
+ else {
291
+ let existingEntries = [...currentHooks.entries()].filter(([k]) => k !== key);
292
+ currentHooks.clear(), currentHooks.set(key, callback);
293
+ for (let [k, v] of existingEntries)
294
+ currentHooks.set(k, v);
295
+ }
296
+ return this;
297
+ }
298
+ main(callback) {
299
+ return this.setHook("main", "MAIN" /* Main */, callback);
300
+ }
301
+ pre(callback) {
302
+ return this.setHook("pre", "PRE" /* Pre */, callback);
303
+ }
304
+ post(callback) {
305
+ return this.setHook("post", "POST" /* Post */, callback);
306
+ }
307
+ error(callback) {
308
+ return this.setHook("error", "ERROR" /* Error */, callback);
309
+ }
310
+ async execute(context, ...args) {
311
+ let pipeline = [
312
+ ...this.hooks.PRE.values(),
313
+ ...this.hooks.MAIN.values(),
314
+ ...this.hooks.POST.values()
315
+ ], error;
316
+ for (let hook of pipeline) {
317
+ if (context.canceled)
318
+ break;
319
+ try {
320
+ await hook(context, ...args);
321
+ } catch (e) {
322
+ error = e;
323
+ break;
324
+ }
325
+ }
326
+ if (!error)
327
+ return;
328
+ if (!this.hooks.ERROR.size)
329
+ throw error;
330
+ for (let [key, callback] of this.hooks.ERROR.entries()) {
331
+ if (context.canceled)
332
+ break;
333
+ try {
334
+ await callback(context, error, ...args);
335
+ } catch (innerError) {
336
+ console.error(`[Lifecycle] Error handler for '${key}' failed:`, innerError);
337
+ }
338
+ }
339
+ }
340
+ };
341
+ var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z.object({
342
+ name: z.string(),
343
+ description: z.string().optional(),
344
+ required: z.boolean().default(true)
345
+ }), StringParamSchema = BaseParamSchema.extend({
346
+ maxLength: z.number().min(1).optional(),
347
+ minLength: z.number().min(1).optional()
348
+ }), NumberParamSchema = BaseParamSchema.extend({
349
+ maxValue: z.number().optional(),
350
+ minValue: z.number().optional()
351
+ }), UserParamSchema = BaseParamSchema.extend({});
352
+
353
+ // src/errors/BakitError.ts
354
+ var BakitError = class extends Error {
355
+ constructor(message) {
356
+ super(message), this.name = this.constructor.name, Object.setPrototypeOf(this, new.target.prototype);
357
+ }
358
+ };
359
+
360
+ // src/errors/ArgumentError.ts
361
+ var ArgumentError = class extends BakitError {
362
+ constructor(target, reason) {
363
+ super(`Invalid argument for '${target}': ${reason}`);
364
+ this.target = target;
365
+ this.reason = reason;
366
+ }
367
+ };
368
+
369
+ // src/base/command/param/Param.ts
370
+ var BaseParam = class {
371
+ constructor(options, schema) {
372
+ this.schema = schema;
373
+ let parsed = schema.parse({
374
+ ...options,
375
+ description: options.description ?? options.name
376
+ });
377
+ this.options = parsed;
378
+ }
379
+ options;
380
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
+ setOption(key, value) {
382
+ if (value === null)
383
+ return delete this.options[key], this;
384
+ let fieldValidator = this.schema.shape[key];
385
+ if (!fieldValidator)
386
+ return this.options[key] = value, this;
387
+ let parsedValue = fieldValidator.parse(value);
388
+ return this.options[key] = parsedValue, this;
389
+ }
390
+ name(value) {
391
+ return this.setOption("name", value);
392
+ }
393
+ description(value) {
394
+ return this.setOption("description", value);
395
+ }
396
+ required(value) {
397
+ return this.setOption("required", value);
398
+ }
399
+ async resolve(context, value) {
400
+ let { required, name } = this.options;
401
+ if (value === void 0) {
402
+ if (required)
403
+ throw new ArgumentError(name, "is required");
404
+ return null;
405
+ }
406
+ if (context.isChatInput())
407
+ return await this.resolveChatInput(context);
408
+ if (context.isMessage())
409
+ return await this.resolveMessage(context, value);
410
+ throw new Error("Invalid context type provided");
411
+ }
412
+ /**
413
+ * Helper to normalize string inputs into an options object.
414
+ */
415
+ static getOptions(options) {
416
+ return typeof options == "string" ? { name: options } : options;
417
+ }
418
+ }, StringParam = class extends BaseParam {
419
+ constructor(options) {
420
+ super(BaseParam.getOptions(options), StringParamSchema);
421
+ }
422
+ required(value) {
423
+ return super.required(value);
424
+ }
425
+ resolveMessage(_context, value) {
426
+ let { minLength, maxLength, name } = this.options;
427
+ if (minLength && value.length < minLength)
428
+ throw new ArgumentError(name, `must be at least ${minLength} chars long`);
429
+ if (maxLength && value.length > maxLength)
430
+ throw new ArgumentError(name, `must be at most ${maxLength} chars long`);
431
+ return value;
432
+ }
433
+ resolveChatInput(context) {
434
+ let { name, required } = this.options;
435
+ return context.source.options.getString(name, required);
436
+ }
437
+ /**
438
+ * Sets the minimum allowed length for this string.
439
+ * Pass `null` to remove this constraint.
440
+ */
441
+ min(length) {
442
+ return this.setOption("minLength", length);
443
+ }
444
+ /**
445
+ * Sets the maximum allowed length for this string.
446
+ * Pass `null` to remove this constraint.
447
+ */
448
+ max(length) {
449
+ return this.setOption("maxLength", length);
450
+ }
451
+ }, NumberParam = class extends BaseParam {
452
+ constructor(options) {
453
+ super(BaseParam.getOptions(options), NumberParamSchema);
454
+ }
455
+ required(value) {
456
+ return super.required(value);
457
+ }
458
+ resolveMessage(_context, value) {
459
+ let { minValue, maxValue, name } = this.options, num = Number(value);
460
+ if (isNaN(num))
461
+ throw new ArgumentError(name, "must be a number");
462
+ if (minValue !== void 0 && num < minValue)
463
+ throw new ArgumentError(name, `must be greater than ${minValue}`);
464
+ if (maxValue !== void 0 && num > maxValue)
465
+ throw new ArgumentError(name, `must be less than ${minValue}`);
466
+ return num;
467
+ }
468
+ resolveChatInput(context) {
469
+ let { name, required } = this.options;
470
+ return context.source.options.getString(name, required);
471
+ }
472
+ /**
473
+ * Sets the minimum allowed value for this number.
474
+ * Pass `null` to remove this constraint.
475
+ */
476
+ min(value) {
477
+ return this.setOption("minValue", value);
478
+ }
479
+ /**
480
+ * Sets the maximum allowed value for this number.
481
+ * Pass `null` to remove this constraint.
482
+ */
483
+ max(value) {
484
+ return this.setOption("maxValue", value);
485
+ }
486
+ }, UserParam = class extends BaseParam {
487
+ constructor(options) {
488
+ super(BaseParam.getOptions(options), UserParamSchema);
489
+ }
490
+ required(value) {
491
+ return super.required(value);
492
+ }
493
+ async resolveMessage(context, value) {
494
+ let id = extractSnowflakeId(value);
495
+ if (!id)
496
+ return null;
497
+ let { users } = context.client;
498
+ return await users.fetch(id).catch(() => null);
499
+ }
500
+ resolveChatInput(context) {
501
+ let { name, required } = this.options;
502
+ return context.source.options.getUser(name, required);
503
+ }
504
+ };
505
+ function validateParamsOrder(params) {
506
+ let seenOptional = false;
507
+ for (let param of params)
508
+ if (param.options.required) {
509
+ if (seenOptional)
510
+ return false;
511
+ } else
512
+ seenOptional = true;
513
+ return true;
514
+ }
515
+ var CommandOptionsSchema = z.object({
516
+ name: z.string().readonly(),
517
+ description: z.string().min(1).max(100).optional().readonly(),
518
+ nsfw: z.boolean().default(false).readonly(),
519
+ params: z.array(z.instanceof(BaseParam)).default([]).readonly(),
520
+ quotes: z.boolean().default(true).readonly()
521
+ }).transform((data) => ({
522
+ ...data,
523
+ description: data.description ?? `Command ${data.name}`
524
+ })).refine(({ params }) => validateParamsOrder(params), {
525
+ path: ["params"],
526
+ error: "Required params must be placed before optional params"
527
+ }), Command = class extends LifecycleManager {
528
+ constructor(options) {
529
+ let _options = CommandOptionsSchema.parse(typeof options == "string" ? { name: options } : options);
530
+ super(`command:${_options.name}`), this.options = _options, this.setHook("syntaxError", "ERROR" /* Error */, async (ctx, error, ...args) => {
531
+ await this.handleSyntaxError(ctx, error, args);
532
+ });
533
+ }
534
+ async handleSyntaxError(context, error, _args) {
535
+ error instanceof BakitError && await context.send(error.message);
536
+ }
537
+ toSlashCommandJSON() {
538
+ let { name, description, nsfw, params } = this.options, builder = new SlashCommandBuilder().setName(name).setDescription(description).setNSFW(nsfw);
539
+ return this.initSlashCommandOptions(builder, params), builder.toJSON();
540
+ }
541
+ initSlashCommandOptions(builder, params) {
542
+ for (let param of params)
543
+ this.initSlashCommandOption(builder, param);
544
+ }
545
+ initSlashCommandOption(builder, param) {
546
+ let initOption = (builder2) => {
547
+ let { name, description, required } = param.options;
548
+ return builder2.setName(name).setDescription(description).setRequired(required);
549
+ };
550
+ if (param instanceof StringParam) {
551
+ let { maxLength, minLength } = param.options, option = initOption(new SlashCommandStringOption());
552
+ maxLength && option.setMaxLength(maxLength), minLength && option.setMinLength(minLength), builder.addStringOption(option);
553
+ return;
554
+ }
555
+ if (param instanceof NumberParam) {
556
+ let { maxValue, minValue } = param.options, option = initOption(new SlashCommandNumberOption());
557
+ maxValue && option.setMaxValue(maxValue), minValue && option.setMinValue(minValue), builder.addNumberOption(option);
558
+ return;
559
+ }
560
+ if (param instanceof UserParam) {
561
+ let option = initOption(new SlashCommandUserOption());
562
+ builder.addUserOption(option);
563
+ return;
564
+ }
565
+ }
566
+ };
567
+ function defineCommand(options) {
568
+ return new Command(options);
569
+ }
570
+
571
+ // src/base/command/CommandManager.ts
572
+ var CommandManager = class extends BaseClientManager {
573
+ commands = new Collection();
574
+ async loadModules() {
575
+ let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "commands", "**/*.{ts,js}"), loads = (await glob(pattern, {
576
+ cwd: process.cwd(),
577
+ absolute: true
578
+ })).map(async (file) => {
579
+ try {
580
+ let command = await $jiti.import(file, { default: !0 });
581
+ if (!command) {
582
+ console.warn(`[Loader] File has no default export: ${file}`);
583
+ return;
584
+ }
585
+ if (!(command instanceof Command)) {
586
+ console.warn(`[Loader] Default export is not a Command: ${file}`);
587
+ return;
588
+ }
589
+ return this.add(command), command;
590
+ } catch (error) {
591
+ console.error(`An error occurred while trying to add command for '${file}':`, error);
592
+ }
593
+ }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
594
+ return console.log(`Loaded ${loaded.length} command(s).`), loaded;
595
+ }
596
+ add(command) {
597
+ if (!(command instanceof Command))
598
+ throw new Error("Invalid command provided");
599
+ let { name } = command.options;
600
+ if (this.commands.has(name)) {
601
+ console.warn(`[Loader] Duplicate command registered: '${name}'`);
602
+ return;
603
+ }
604
+ this.commands.set(name, command);
605
+ }
606
+ remove(target) {
607
+ let name = typeof target == "string" ? target : target.options.name, existing = this.commands.get(name);
608
+ if (existing)
609
+ return this.commands.delete(name), existing;
610
+ }
611
+ get(name) {
612
+ return this.commands.get(name);
613
+ }
614
+ };
615
+ var ListenerOptionsSchema = z4.object({
616
+ name: z4.enum(Events),
617
+ once: z4.boolean().default(false)
618
+ }), Listener = class extends LifecycleManager {
619
+ options;
35
620
  constructor(options) {
621
+ let _options = ListenerOptionsSchema.parse(typeof options == "string" ? { name: options } : options);
622
+ super(`listener:${_options.name}`), this.options = _options;
623
+ }
624
+ };
625
+ function defineListener(options) {
626
+ return new Listener(options);
627
+ }
628
+ var ListenerManager = class extends BaseClientManager {
629
+ listeners = [];
630
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
631
+ executors = /* @__PURE__ */ new WeakMap();
632
+ async loadModules() {
633
+ let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "listeners", "**/*.{ts,js}"), loads = (await glob(pattern, {
634
+ cwd: process.cwd(),
635
+ absolute: true
636
+ })).map(async (file) => {
637
+ try {
638
+ let listener = await $jiti.import(file, { default: !0 });
639
+ if (!listener) {
640
+ console.warn(`[Loader] File has no default export: ${file}`);
641
+ return;
642
+ }
643
+ if (!(listener instanceof Listener)) {
644
+ console.warn(`[Loader] Default export is not a Listener: ${file}`);
645
+ return;
646
+ }
647
+ return this.add(listener), listener;
648
+ } catch (error) {
649
+ console.error(`An error occurred while trying to add listener for '${file}':`, error);
650
+ }
651
+ }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
652
+ return console.log(`Loaded ${loaded.length} listener(s).`), loaded;
653
+ }
654
+ add(listener) {
655
+ if (!(listener instanceof Listener))
656
+ throw new Error("Invalid listener provided");
657
+ let execute = (...args) => {
658
+ listener.execute(new Context(), ...args);
659
+ };
660
+ this.listeners.push(listener), this.executors.set(listener, execute);
661
+ let { once, name } = listener.options;
662
+ this.client[once ? "once" : "on"](name, execute);
663
+ }
664
+ remove(target) {
665
+ let isMatched = (listener) => typeof target == "string" ? listener.options.name === target : listener === target, removed = [];
666
+ return this.listeners = this.listeners.filter((listener) => {
667
+ if (!isMatched(listener))
668
+ return true;
669
+ removed.push(listener);
670
+ let execute = this.executors.get(listener);
671
+ return execute && (this.client.removeListener(listener.options.name, execute), this.executors.delete(listener)), false;
672
+ }), removed;
673
+ }
674
+ getBaseIntents() {
675
+ return new IntentsBitField([GatewayIntentBits.Guilds]);
676
+ }
677
+ getNeededIntents() {
678
+ let result = this.getBaseIntents();
679
+ for (let listener of this.listeners) {
680
+ let eventName = listener.options.name, requiredIntents = EVENT_INTENT_MAPPING[eventName];
681
+ requiredIntents && result.add(requiredIntents);
682
+ }
683
+ return result;
684
+ }
685
+ };
686
+
687
+ // src/base/BakitClient.ts
688
+ var BakitClient2 = class extends Client {
689
+ constructor(options, instance) {
36
690
  super(options);
691
+ this.instance = instance;
692
+ this.managers = {
693
+ commands: new CommandManager(this),
694
+ listeners: new ListenerManager(this)
695
+ };
37
696
  }
697
+ managers;
698
+ /**
699
+ * Check if the client is connected to gateway successfully and finished initialization.
700
+ */
38
701
  isReady() {
39
702
  return super.isReady();
40
703
  }
@@ -56,6 +719,146 @@ var BakitClient = class extends Client {
56
719
  emit(event, ...args) {
57
720
  return super.emit(event, ...args);
58
721
  }
722
+ /**
723
+ * Override BakitClient output when using logger for security concern.
724
+ * @returns `BakitClient {}`
725
+ */
726
+ [inspect.custom]() {
727
+ return `${this.constructor.name} {}`;
728
+ }
59
729
  };
60
730
 
61
- export { BakitClient, defineConfig, getConfig, loadConfig };
731
+ // src/base/command/param/Params.ts
732
+ function createFactory(ctor) {
733
+ return (...args) => new ctor(...args);
734
+ }
735
+ var Params = {
736
+ string: createFactory(StringParam),
737
+ number: createFactory(NumberParam)
738
+ };
739
+
740
+ // src/defaults/command.ts
741
+ var messageCommandHandler = defineListener(Events.MessageCreate), chatInputCommandHandler = defineListener(Events.InteractionCreate), registerCommandsHandler = defineListener({
742
+ name: Events.ClientReady,
743
+ once: true
744
+ });
745
+ registerCommandsHandler.main(async (_, client) => {
746
+ let { managers, instance } = client, { commands } = managers, { cache } = instance, payload = commands.commands.map((cmd) => cmd.toSlashCommandJSON()).sort((a, b) => a.name.localeCompare(b.name)), currentHash = cache.getHash(payload), CACHE_KEY = "commands/meta.json", cachedMeta = await cache.read(CACHE_KEY);
747
+ if (cachedMeta && cachedMeta.hash === currentHash) {
748
+ let { timestamp, count } = cachedMeta, time = new Date(timestamp).toLocaleString();
749
+ console.log(`${count} command(s) are up to date (Last sync: ${time}). Skipping registration.`);
750
+ return;
751
+ }
752
+ try {
753
+ let result = await client.application.commands.set(payload);
754
+ cache.write(CACHE_KEY, {
755
+ hash: currentHash,
756
+ timestamp: Date.now(),
757
+ count: result.size
758
+ }), cache.write("commands/debug_dump.json", payload), console.log(`Registered ${result.size} application command(s).`);
759
+ } catch (error) {
760
+ console.error("Failed to register commands:", error);
761
+ }
762
+ });
763
+ messageCommandHandler.main(async (_, message) => {
764
+ let config = getConfig();
765
+ if (message.author.bot)
766
+ return;
767
+ let { content } = message, client = message.client, lowerContent = content.toLowerCase(), prefix = config.prefixes.find((p) => lowerContent.startsWith(p));
768
+ if (!prefix)
769
+ return;
770
+ let [name, ...args] = content.slice(prefix.length).trim().split(/\s+/g);
771
+ if (!name)
772
+ return;
773
+ let command = client.managers.commands.get(name);
774
+ if (!command)
775
+ return;
776
+ let context = new MessageContext(message), { params, quotes } = command.options, rawArgs = quotes ? tokenize(args.join(" ")) : args, resolvedArgs = [];
777
+ for (let i = 0; i < params.length; i++) {
778
+ let param = params[i], arg = rawArgs[i];
779
+ if (!param)
780
+ break;
781
+ let resolved = await param.resolve(context, arg);
782
+ resolvedArgs.push(resolved);
783
+ }
784
+ await command.execute(context, ...resolvedArgs);
785
+ });
786
+ chatInputCommandHandler.main(async (_, interaction) => {
787
+ if (!interaction.isChatInputCommand())
788
+ return;
789
+ let { commandName } = interaction, command = interaction.client.managers.commands.get(commandName);
790
+ if (!command)
791
+ return;
792
+ let context = new ChatInputContext(interaction), { params } = command.options, resolvedArgs = [];
793
+ for (let param of params) {
794
+ let resolved = await param.resolve(context);
795
+ resolvedArgs.push(resolved);
796
+ }
797
+ await command.execute(context, ...resolvedArgs);
798
+ });
799
+ var ProjectCacheManager = class {
800
+ rootDir;
801
+ constructor(root = process.cwd()) {
802
+ this.rootDir = join(root, ".bakit"), this.ensureRoot();
803
+ }
804
+ ensureRoot() {
805
+ existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
806
+ }
807
+ getHash(data) {
808
+ return createHash("sha256").update(JSON.stringify(data)).digest("hex");
809
+ }
810
+ async write(path, data) {
811
+ let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
812
+ await mkdir(dir, { recursive: true });
813
+ let content = typeof data == "string" ? data : JSON.stringify(data);
814
+ await writeFile(fullPath, content, "utf-8");
815
+ }
816
+ async read(path) {
817
+ let fullPath = join(this.rootDir, path);
818
+ try {
819
+ let content = await readFile(fullPath, "utf-8");
820
+ return JSON.parse(content);
821
+ } catch {
822
+ return null;
823
+ }
824
+ }
825
+ async clear() {
826
+ await rm(this.rootDir, { recursive: true, force: true });
827
+ }
828
+ clearSync() {
829
+ existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
830
+ }
831
+ };
832
+
833
+ // src/base/Instance.ts
834
+ var Instance = class {
835
+ client;
836
+ cache;
837
+ constructor() {
838
+ this.cache = new ProjectCacheManager();
839
+ }
840
+ async start() {
841
+ await loadConfig();
842
+ let config = getConfig();
843
+ this.client = new BakitClient2(
844
+ {
845
+ intents: [],
846
+ ...config.clientOptions
847
+ },
848
+ this
849
+ ), await this.loadModules(), this.initIntents(), await this.client.login(config.token);
850
+ }
851
+ loadModules() {
852
+ let { managers } = this.client, { commands, listeners } = managers;
853
+ return listeners.add(chatInputCommandHandler), listeners.add(messageCommandHandler), listeners.add(registerCommandsHandler), Promise.all([commands.loadModules(), listeners.loadModules()]);
854
+ }
855
+ initIntents() {
856
+ let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
857
+ config.intents === "auto" ? intents = listeners.getNeededIntents() : (intents = listeners.getBaseIntents(), typeof config.intents == "bigint" ? intents.bitfield = Number(config.intents) : intents.add(...config.intents)), options.intents = intents;
858
+ }
859
+ };
860
+ function useApp() {
861
+ return new Instance();
862
+ }
863
+
864
+ export { $jiti, ArgumentError, BakitClient2 as BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectConfigSchema, StringParam, StringParamSchema, UserParam, UserParamSchema, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, loadConfig, tokenize, useApp, validateParamsOrder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bakit",
3
- "version": "2.0.0-alpha.1",
3
+ "version": "2.0.0-alpha.10",
4
4
  "description": "A framework for discord.js",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -10,6 +10,9 @@
10
10
  "type-check": "tsc --noEmit",
11
11
  "test": "vitest run --pass-with-no-tests"
12
12
  },
13
+ "bin": {
14
+ "bakit": "./bin/bakit.js"
15
+ },
13
16
  "files": [
14
17
  "dist"
15
18
  ],
@@ -34,11 +37,12 @@
34
37
  "discord.js": "^14.0.0"
35
38
  },
36
39
  "dependencies": {
40
+ "commander": "^14.0.2",
41
+ "dotenv": "^17.2.1",
42
+ "jiti": "^2.6.1",
43
+ "nodemon": "^3.1.11",
37
44
  "tiny-glob": "^0.2.9",
38
45
  "type-fest": "^4.41.0",
39
46
  "zod": "^4.1.12"
40
- },
41
- "devDependencies": {
42
- "@swc/core": "^1.13.5"
43
47
  }
44
48
  }