bakit 2.0.0-alpha.2 → 2.0.0-alpha.21

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/dist/index.js CHANGED
@@ -1,134 +1,306 @@
1
- import { GatewayIntentBits, Events, Client, Collection, ChatInputCommandInteraction, Message } from 'discord.js';
2
- import z3, { z } from 'zod';
3
- import { pathToFileURL } from 'url';
4
- import glob from 'tiny-glob';
1
+ import { GatewayIntentBits, Events, Client, Collection, IntentsBitField, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
5
2
  import { inspect } from 'util';
6
- import { posix } from 'path';
3
+ import { posix, relative, sep, join, dirname } from 'path';
4
+ import glob from 'tiny-glob';
5
+ import z4 from 'zod';
6
+ import { pathToFileURL } from 'url';
7
+ import { existsSync, mkdirSync, rmSync } from 'fs';
8
+ import { mkdir, writeFile, readFile, rm } from 'fs/promises';
9
+ import { createHash } from 'crypto';
7
10
 
8
- // src/config.ts
9
- var ProjectConfigSchema = z.object({
10
- /**
11
- * The gateway intents to use for the Discord client.
12
- *
13
- * - `auto` — automatically determine the required intents.
14
- * - bigint — a raw bitfield value representing the combined intents.
15
- * - array — a list of individual intent flags from `GatewayIntentBits`.
16
- *
17
- * @defaultvalue `auto`
18
- */
19
- intents: z.union([z.literal("auto"), z.bigint(), z.array(z.enum(GatewayIntentBits))]).default("auto"),
20
- /**
21
- * Optional custom client options for Discord.js (excluding `intents`).
22
- *
23
- * These are passed directly to the `Client` constructor when initializing the bot.
24
- *
25
- * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
26
- */
27
- clientOptions: z.custom().optional(),
28
- /**
29
- * The path to the main project source directory.
30
- *
31
- * @defaultvalue `src`
32
- */
33
- entryDir: z.string().default("src")
34
- });
35
- function defineConfig(config) {
36
- return config;
37
- }
38
- var _config;
39
- async function loadConfig(cwd = process.cwd()) {
40
- if (_config)
41
- return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
42
- let globPattern = `bakit.config.{${["ts", "js"].join(",")}}`, [configPath, other] = await glob(globPattern, {
43
- cwd: cwd.replace(/\\/g, "/"),
44
- // ensure the path uses `/` instead of `\` on Windows
45
- absolute: true
46
- });
47
- if (!configPath)
48
- throw new Error("Missing config file");
49
- other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
50
- let configFileURL = pathToFileURL(configPath).toString(), { default: config } = await import(configFileURL);
51
- return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
52
- }
53
- function getConfig() {
54
- if (!_config)
55
- throw new Error("Project config is not loaded.");
56
- return _config;
11
+ // src/core/client/BakitClient.ts
12
+ var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z4.object({
13
+ name: z4.string(),
14
+ description: z4.string().optional(),
15
+ required: z4.boolean().default(true)
16
+ }), StringParamSchema = BaseParamSchema.extend({
17
+ maxLength: z4.number().min(1).optional(),
18
+ minLength: z4.number().min(1).optional()
19
+ }), NumberParamSchema = BaseParamSchema.extend({
20
+ maxValue: z4.number().optional(),
21
+ minValue: z4.number().optional()
22
+ }), UserParamSchema = BaseParamSchema.extend({});
23
+ var INTENT_GROUPS = {
24
+ [GatewayIntentBits.Guilds]: [
25
+ Events.GuildCreate,
26
+ Events.GuildDelete,
27
+ Events.GuildUpdate,
28
+ Events.GuildUnavailable,
29
+ Events.GuildRoleCreate,
30
+ Events.GuildRoleDelete,
31
+ Events.GuildRoleUpdate,
32
+ Events.ChannelCreate,
33
+ Events.ChannelDelete,
34
+ Events.ChannelUpdate,
35
+ Events.ChannelPinsUpdate,
36
+ Events.ThreadCreate,
37
+ Events.ThreadDelete,
38
+ Events.ThreadUpdate,
39
+ Events.ThreadListSync,
40
+ Events.ThreadMemberUpdate,
41
+ Events.ThreadMembersUpdate,
42
+ Events.StageInstanceCreate,
43
+ Events.StageInstanceUpdate,
44
+ Events.StageInstanceDelete
45
+ ],
46
+ [GatewayIntentBits.GuildMembers]: [
47
+ Events.GuildMemberAdd,
48
+ Events.GuildMemberUpdate,
49
+ Events.GuildMemberRemove,
50
+ Events.ThreadMembersUpdate
51
+ ],
52
+ [GatewayIntentBits.GuildModeration]: [Events.GuildAuditLogEntryCreate, Events.GuildBanAdd, Events.GuildBanRemove],
53
+ [GatewayIntentBits.GuildExpressions]: [
54
+ Events.GuildEmojiCreate,
55
+ Events.GuildEmojiDelete,
56
+ Events.GuildEmojiUpdate,
57
+ Events.GuildStickerCreate,
58
+ Events.GuildStickerDelete,
59
+ Events.GuildStickerUpdate,
60
+ Events.GuildSoundboardSoundCreate,
61
+ Events.GuildSoundboardSoundUpdate,
62
+ Events.GuildSoundboardSoundDelete,
63
+ Events.GuildSoundboardSoundsUpdate
64
+ ],
65
+ [GatewayIntentBits.GuildIntegrations]: [Events.GuildIntegrationsUpdate],
66
+ [GatewayIntentBits.GuildWebhooks]: [Events.WebhooksUpdate],
67
+ [GatewayIntentBits.GuildInvites]: [Events.InviteCreate, Events.InviteDelete],
68
+ [GatewayIntentBits.GuildVoiceStates]: [Events.VoiceStateUpdate],
69
+ [GatewayIntentBits.GuildPresences]: [Events.PresenceUpdate],
70
+ [GatewayIntentBits.GuildMessages]: [
71
+ Events.MessageCreate,
72
+ Events.MessageUpdate,
73
+ Events.MessageDelete,
74
+ Events.MessageBulkDelete
75
+ ],
76
+ [GatewayIntentBits.GuildMessageReactions]: [
77
+ Events.MessageReactionAdd,
78
+ Events.MessageReactionRemove,
79
+ Events.MessageReactionRemoveAll,
80
+ Events.MessageReactionRemoveEmoji
81
+ ],
82
+ [GatewayIntentBits.GuildMessageTyping]: [Events.TypingStart],
83
+ [GatewayIntentBits.DirectMessages]: [
84
+ Events.MessageCreate,
85
+ Events.MessageUpdate,
86
+ Events.MessageDelete,
87
+ Events.ChannelPinsUpdate
88
+ ],
89
+ [GatewayIntentBits.DirectMessageReactions]: [
90
+ Events.MessageReactionAdd,
91
+ Events.MessageReactionRemove,
92
+ Events.MessageReactionRemoveAll,
93
+ Events.MessageReactionRemoveEmoji
94
+ ],
95
+ [GatewayIntentBits.DirectMessageTyping]: [Events.TypingStart],
96
+ [GatewayIntentBits.MessageContent]: [Events.MessageCreate, Events.MessageUpdate],
97
+ [GatewayIntentBits.GuildScheduledEvents]: [
98
+ Events.GuildScheduledEventCreate,
99
+ Events.GuildScheduledEventDelete,
100
+ Events.GuildScheduledEventUpdate,
101
+ Events.GuildScheduledEventUserAdd,
102
+ Events.GuildScheduledEventUserRemove
103
+ ],
104
+ [GatewayIntentBits.AutoModerationConfiguration]: [
105
+ Events.AutoModerationRuleCreate,
106
+ Events.AutoModerationRuleDelete,
107
+ Events.AutoModerationRuleUpdate
108
+ ],
109
+ [GatewayIntentBits.AutoModerationExecution]: [Events.AutoModerationActionExecution],
110
+ [GatewayIntentBits.GuildMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove],
111
+ [GatewayIntentBits.DirectMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove]
112
+ }, EVENT_INTENT_MAPPING = {};
113
+ for (let [intentStr, events] of Object.entries(INTENT_GROUPS)) {
114
+ let intent = Number(intentStr);
115
+ for (let event of events)
116
+ EVENT_INTENT_MAPPING[event] ??= [], EVENT_INTENT_MAPPING[event].includes(intent) || EVENT_INTENT_MAPPING[event].push(intent);
57
117
  }
58
118
 
59
- // src/base/lifecycle/Context.ts
60
- var Context = class {
61
- canceled = false;
62
- cancel() {
63
- this.canceled = true;
119
+ // src/lib/errors/BakitError.ts
120
+ var BakitError = class extends Error {
121
+ constructor(message) {
122
+ super(message), this.name = this.constructor.name, Object.setPrototypeOf(this, new.target.prototype);
64
123
  }
65
124
  };
66
125
 
67
- // src/command/CommandContext.ts
68
- var BaseCommandContext = class extends Context {
69
- constructor(source) {
70
- super();
71
- this.source = source;
126
+ // src/lib/errors/ArgumentError.ts
127
+ var ArgumentError = class extends BakitError {
128
+ constructor(target, reason) {
129
+ super(`Invalid argument for '${target}': ${reason}`);
130
+ this.target = target;
131
+ this.reason = reason;
72
132
  }
73
- get client() {
74
- return this.source.client;
133
+ };
134
+
135
+ // src/lib/utils/string.ts
136
+ function tokenize(content) {
137
+ let args = [], current = "", quoteChar = null, isEscaped = false;
138
+ for (let i = 0; i < content.length; i++) {
139
+ let char = content[i];
140
+ if (char === void 0)
141
+ break;
142
+ if (isEscaped) {
143
+ current += char, isEscaped = false;
144
+ continue;
145
+ }
146
+ if (char === "\\") {
147
+ isEscaped = true;
148
+ continue;
149
+ }
150
+ quoteChar ? char === quoteChar ? quoteChar = null : current += char : char === '"' ? quoteChar = char : /\s/.test(char) ? current.length > 0 && (args.push(current), current = "") : current += char;
75
151
  }
76
- get channel() {
77
- return this.source.channel;
152
+ return current.length > 0 && args.push(current), args;
153
+ }
154
+ function extractSnowflakeId(input) {
155
+ if (!input) return null;
156
+ let mentionMatch = /^<@!?(\d{17,20})>$/.exec(input);
157
+ if (mentionMatch?.[1])
158
+ return mentionMatch[1];
159
+ let idMatch = /^(\d{17,20})$/.exec(input);
160
+ return idMatch?.[1] ? idMatch[1] : null;
161
+ }
162
+ function getTopLevelDirectory(path, entryDir) {
163
+ return relative(entryDir, path).split(sep)[0] ?? null;
164
+ }
165
+
166
+ // src/core/structures/param/Param.ts
167
+ var BaseParam = class {
168
+ constructor(options, schema) {
169
+ this.schema = schema;
170
+ let parsed = schema.parse({
171
+ ...options,
172
+ description: options.description ?? options.name
173
+ });
174
+ this.options = parsed;
78
175
  }
79
- get channelId() {
80
- return this.source.channelId;
176
+ options;
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ setOption(key, value) {
179
+ if (value === null)
180
+ return delete this.options[key], this;
181
+ let fieldValidator = this.schema.shape[key];
182
+ if (!fieldValidator)
183
+ return this.options[key] = value, this;
184
+ let parsedValue = fieldValidator.parse(value);
185
+ return this.options[key] = parsedValue, this;
81
186
  }
82
- get guild() {
83
- return this.source.guild;
187
+ name(value) {
188
+ return this.setOption("name", value);
84
189
  }
85
- get guildId() {
86
- return this.source.guildId;
190
+ description(value) {
191
+ return this.setOption("description", value);
87
192
  }
88
- get member() {
89
- return this.source.member;
193
+ required(value) {
194
+ return this.setOption("required", value);
90
195
  }
91
- inGuild() {
92
- return !!this.guildId;
196
+ async resolve(context, value) {
197
+ if (context.isChatInput())
198
+ return await this.resolveChatInput(context);
199
+ if (context.isMessage()) {
200
+ let { required, name } = this.options;
201
+ if (value === void 0) {
202
+ if (required)
203
+ throw new ArgumentError(name, "is required");
204
+ return null;
205
+ }
206
+ return await this.resolveMessage(context, value);
207
+ }
208
+ throw new Error("Invalid context type provided");
93
209
  }
94
- inCachedGuild() {
95
- if (this.isChatInput())
96
- return this.source.inCachedGuild();
97
- if (this.isMessage())
98
- return this.source.inGuild();
99
- throw new Error("Invalid source");
210
+ /**
211
+ * Helper to normalize string inputs into an options object.
212
+ */
213
+ static getOptions(options) {
214
+ return typeof options == "string" ? { name: options } : options;
100
215
  }
101
- get user() {
102
- if (this.isChatInput())
103
- return this.source.user;
104
- if (this.isMessage())
105
- return this.source.author;
106
- throw new Error("Invalid source");
216
+ }, StringParam = class extends BaseParam {
217
+ constructor(options) {
218
+ super(BaseParam.getOptions(options), StringParamSchema);
107
219
  }
108
- isChatInput() {
109
- return this.source instanceof ChatInputCommandInteraction;
220
+ required(value) {
221
+ return super.required(value);
110
222
  }
111
- isMessage() {
112
- return this.source instanceof Message;
223
+ resolveMessage(_context, value) {
224
+ let { minLength, maxLength, name } = this.options;
225
+ if (minLength && value.length < minLength)
226
+ throw new ArgumentError(name, `must be at least ${minLength} chars long`);
227
+ if (maxLength && value.length > maxLength)
228
+ throw new ArgumentError(name, `must be at most ${maxLength} chars long`);
229
+ return value;
113
230
  }
114
- }, ChatInputContext = class extends BaseCommandContext {
115
- async send(options) {
116
- typeof options == "string" && (options = { content: options });
117
- let sendOptions = {
118
- ...options,
119
- withResponse: true
120
- };
121
- return this.source.deferred || this.source.replied ? await this.source.followUp(sendOptions) : (await this.source.reply(sendOptions)).resource?.message;
231
+ resolveChatInput(context) {
232
+ let { name, required } = this.options;
233
+ return context.source.options.getString(name, required);
122
234
  }
123
- }, MessageContext = class extends BaseCommandContext {
124
- async send(options) {
125
- let { channel } = this;
126
- if (!channel?.isSendable())
127
- throw new Error("Invalid channel or channel is not sendable");
128
- return await channel.send(options);
235
+ /**
236
+ * Sets the minimum allowed length for this string.
237
+ * Pass `null` to remove this constraint.
238
+ */
239
+ min(length) {
240
+ return this.setOption("minLength", length);
241
+ }
242
+ /**
243
+ * Sets the maximum allowed length for this string.
244
+ * Pass `null` to remove this constraint.
245
+ */
246
+ max(length) {
247
+ return this.setOption("maxLength", length);
248
+ }
249
+ }, NumberParam = class extends BaseParam {
250
+ constructor(options) {
251
+ super(BaseParam.getOptions(options), NumberParamSchema);
252
+ }
253
+ required(value) {
254
+ return super.required(value);
255
+ }
256
+ resolveMessage(_context, value) {
257
+ let { minValue, maxValue, name } = this.options, num = Number(value);
258
+ if (isNaN(num))
259
+ throw new ArgumentError(name, "must be a number");
260
+ if (minValue !== void 0 && num < minValue)
261
+ throw new ArgumentError(name, `must be greater than ${minValue}`);
262
+ if (maxValue !== void 0 && num > maxValue)
263
+ throw new ArgumentError(name, `must be less than ${minValue}`);
264
+ return num;
265
+ }
266
+ resolveChatInput(context) {
267
+ let { name, required } = this.options;
268
+ return context.source.options.getString(name, required);
269
+ }
270
+ /**
271
+ * Sets the minimum allowed value for this number.
272
+ * Pass `null` to remove this constraint.
273
+ */
274
+ min(value) {
275
+ return this.setOption("minValue", value);
276
+ }
277
+ /**
278
+ * Sets the maximum allowed value for this number.
279
+ * Pass `null` to remove this constraint.
280
+ */
281
+ max(value) {
282
+ return this.setOption("maxValue", value);
283
+ }
284
+ }, UserParam = class extends BaseParam {
285
+ constructor(options) {
286
+ super(BaseParam.getOptions(options), UserParamSchema);
287
+ }
288
+ required(value) {
289
+ return super.required(value);
290
+ }
291
+ async resolveMessage(context, value) {
292
+ let id = extractSnowflakeId(value);
293
+ if (!id)
294
+ return null;
295
+ let { users } = context.client;
296
+ return await users.fetch(id).catch(() => null);
297
+ }
298
+ resolveChatInput(context) {
299
+ let { name, required } = this.options;
300
+ return context.source.options.getUser(name, required);
129
301
  }
130
302
  };
131
- var LifecycleManager = class {
303
+ 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 {
132
304
  constructor(id) {
133
305
  this.id = id;
134
306
  }
@@ -199,124 +371,123 @@ var LifecycleManager = class {
199
371
  }
200
372
  };
201
373
 
202
- // src/command/param/Param.ts
203
- var BaseParam = class {
204
- options;
205
- constructor(options) {
206
- this.options = { ...options, required: options.required ?? true };
207
- }
208
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
- setOption(key, value) {
210
- return value === null ? delete this.options[key] : this.options[key] = value, this;
211
- }
212
- name(value) {
213
- return this.setOption("name", value);
214
- }
215
- description(value) {
216
- return this.setOption("description", value);
217
- }
218
- required(value) {
219
- return this.setOption("required", value);
220
- }
221
- /**
222
- * Helper to normalize string inputs into an options object.
223
- */
224
- static getOptions(options) {
225
- return typeof options == "string" ? { name: options } : options;
226
- }
227
- }, StringParam = class extends BaseParam {
228
- constructor(options) {
229
- super(BaseParam.getOptions(options));
230
- }
231
- required(value) {
232
- return super.required(value);
233
- }
234
- /**
235
- * Sets the minimum allowed length for this string.
236
- * Pass `null` to remove this constraint.
237
- */
238
- min(length) {
239
- return this.setOption("minLength", length);
240
- }
241
- /**
242
- * Sets the maximum allowed length for this string.
243
- * Pass `null` to remove this constraint.
244
- */
245
- max(length) {
246
- return this.setOption("maxLength", length);
247
- }
248
- }, NumberParam = class extends BaseParam {
249
- constructor(options) {
250
- super(BaseParam.getOptions(options));
251
- }
252
- required(value) {
253
- return super.required(value);
254
- }
255
- /**
256
- * Sets the minimum allowed value for this number.
257
- * Pass `null` to remove this constraint.
258
- */
259
- min(value) {
260
- return this.setOption("minValue", value);
261
- }
262
- /**
263
- * Sets the maximum allowed value for this number.
264
- * Pass `null` to remove this constraint.
265
- */
266
- max(value) {
267
- return this.setOption("maxValue", value);
268
- }
269
- };
270
-
271
- // src/command/Command.ts
272
- var CommandOptionsSchema = z.object({
273
- name: z.string(),
274
- description: z.string().min(1).max(100).optional(),
275
- params: z.array(z.instanceof(BaseParam)).default([])
374
+ // src/core/structures/Command.ts
375
+ function validateParamsOrder(params) {
376
+ let seenOptional = false;
377
+ for (let param of params)
378
+ if (param.options.required) {
379
+ if (seenOptional)
380
+ return false;
381
+ } else
382
+ seenOptional = true;
383
+ return true;
384
+ }
385
+ var CommandOptionsSchema = z4.object({
386
+ name: z4.string().readonly(),
387
+ description: z4.string().min(1).max(100).optional().readonly(),
388
+ nsfw: z4.boolean().default(false).readonly(),
389
+ params: z4.array(z4.instanceof(BaseParam)).default([]).readonly(),
390
+ quotes: z4.boolean().default(true).readonly()
276
391
  }).transform((data) => ({
277
392
  ...data,
278
393
  description: data.description ?? `Command ${data.name}`
279
- })), Command = class extends LifecycleManager {
394
+ })).refine(({ params }) => validateParamsOrder(params), {
395
+ path: ["params"],
396
+ error: "Required params must be placed before optional params"
397
+ }), Command = class extends LifecycleManager {
280
398
  constructor(options) {
281
399
  let _options = CommandOptionsSchema.parse(typeof options == "string" ? { name: options } : options);
282
- super(`command:${_options.name}`), this.options = _options;
400
+ super(`command:${_options.name}`), this.options = _options, this.setHook("syntaxError", "ERROR" /* Error */, async (ctx, error, ...args) => {
401
+ await this.handleSyntaxError(ctx, error, args);
402
+ });
403
+ }
404
+ async handleSyntaxError(context, error, _args) {
405
+ error instanceof BakitError && await context.send(error.message);
406
+ }
407
+ toSlashCommandJSON() {
408
+ let { name, description, nsfw, params } = this.options, builder = new SlashCommandBuilder().setName(name).setDescription(description).setNSFW(nsfw);
409
+ return this.initSlashCommandOptions(builder, params), builder.toJSON();
410
+ }
411
+ initSlashCommandOptions(builder, params) {
412
+ for (let param of params)
413
+ this.initSlashCommandOption(builder, param);
414
+ }
415
+ initSlashCommandOption(builder, param) {
416
+ let initOption = (builder2) => {
417
+ let { name, description, required } = param.options;
418
+ return builder2.setName(name).setDescription(description).setRequired(required);
419
+ };
420
+ if (param instanceof StringParam) {
421
+ let { maxLength, minLength } = param.options, option = initOption(new SlashCommandStringOption());
422
+ maxLength && option.setMaxLength(maxLength), minLength && option.setMinLength(minLength), builder.addStringOption(option);
423
+ return;
424
+ }
425
+ if (param instanceof NumberParam) {
426
+ let { maxValue, minValue } = param.options, option = initOption(new SlashCommandNumberOption());
427
+ maxValue && option.setMaxValue(maxValue), minValue && option.setMinValue(minValue), builder.addNumberOption(option);
428
+ return;
429
+ }
430
+ if (param instanceof UserParam) {
431
+ let option = initOption(new SlashCommandUserOption());
432
+ builder.addUserOption(option);
433
+ return;
434
+ }
283
435
  }
284
436
  };
285
437
  function defineCommand(options) {
286
438
  return new Command(options);
287
439
  }
288
440
 
289
- // src/base/BaseClientManager.ts
441
+ // src/core/managers/BaseClientManager.ts
290
442
  var BaseClientManager = class {
291
443
  constructor(client) {
292
444
  this.client = client;
293
445
  }
294
446
  };
295
-
296
- // src/command/CommandManager.ts
297
447
  var CommandManager = class extends BaseClientManager {
298
448
  commands = new Collection();
299
- async loadModules() {
300
- let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "commands", "**/*.{ts,js}"), loads = (await glob(pattern, {
301
- cwd: process.cwd()
302
- })).map(async (file) => {
303
- try {
304
- let { default: command } = await import(pathToFileURL(file).toString());
305
- if (!command) {
306
- console.warn(`[Loader] File has no default export: ${file}`);
307
- return;
308
- }
309
- if (!(command instanceof Command)) {
310
- console.warn(`[Loader] Default export is not a Command: ${file}`);
311
- return;
312
- }
313
- return this.add(command), command;
314
- } catch (error) {
315
- console.error(`An error occurred while trying to add command for '${file}':`, error);
316
- }
317
- }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
318
- return console.log(`Loaded ${loaded.length} command(s).`), loaded;
449
+ entries = new Collection();
450
+ async loadModules(entryDir) {
451
+ let pattern = posix.join(posix.resolve(entryDir), "commands", "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((c) => !!c);
452
+ return console.log(`[Loader] Loaded ${filtered.length}/${files.length} command(s)`), filtered;
319
453
  }
454
+ /**
455
+ * Load the file and add the command to the registry.
456
+ * @param path The path to the command file.
457
+ * @returns The command object if added successfully.
458
+ */
459
+ async load(path) {
460
+ let command = (await import(pathToFileURL(path).href)).default;
461
+ if (!command) {
462
+ console.warn(`[Loader] File has no default export: ${path}`);
463
+ return;
464
+ }
465
+ if (!(command instanceof Command)) {
466
+ console.warn(`[Loader] Default export is not a Command: ${path}`);
467
+ return;
468
+ }
469
+ return this.add(command), this.entries.set(path, command), command;
470
+ }
471
+ /**
472
+ * Unload the file and remove the command from the registry.
473
+ * @param path The path to the command file.
474
+ * @returns The command object if unloaded successfully.
475
+ */
476
+ async unload(path) {
477
+ let command = this.entries.get(path);
478
+ if (this.entries.delete(path), (await import('bakit/loader/register')).unload(path), !!command)
479
+ return this.remove(command);
480
+ }
481
+ async reload(path) {
482
+ await this.unload(path);
483
+ let command = await this.load(path);
484
+ if (command)
485
+ return console.log(`[Loader] Reloaded command '${command.options.name}' at '${path}'`), command;
486
+ }
487
+ /**
488
+ * Add a command to the registry.
489
+ * @param command Command to add.
490
+ */
320
491
  add(command) {
321
492
  if (!(command instanceof Command))
322
493
  throw new Error("Invalid command provided");
@@ -327,65 +498,106 @@ var CommandManager = class extends BaseClientManager {
327
498
  }
328
499
  this.commands.set(name, command);
329
500
  }
501
+ /**
502
+ * Remove a command from the registry.
503
+ * @param target Command name or object to remove.
504
+ * @returns The command object if removed successfully.
505
+ */
330
506
  remove(target) {
507
+ if (typeof target != "string" && !(target instanceof Command))
508
+ return;
331
509
  let name = typeof target == "string" ? target : target.options.name, existing = this.commands.get(name);
332
510
  if (existing)
333
511
  return this.commands.delete(name), existing;
334
512
  }
513
+ /**
514
+ * Get a command using its name.
515
+ * @param name The command to get.
516
+ * @returns The command object.
517
+ */
335
518
  get(name) {
336
519
  return this.commands.get(name);
337
520
  }
338
521
  };
339
- var ListenerOptionsSchema = z3.object({
340
- name: z3.enum(Events),
341
- once: z3.boolean().default(false)
522
+ var ListenerOptionsSchema = z4.object({
523
+ name: z4.enum(Events),
524
+ once: z4.boolean().default(false)
342
525
  }), Listener = class extends LifecycleManager {
343
526
  options;
344
527
  constructor(options) {
345
528
  let _options = ListenerOptionsSchema.parse(typeof options == "string" ? { name: options } : options);
346
- super(`listener:${_options.name}`), this.options = options;
529
+ super(`listener:${_options.name}`), this.options = _options;
347
530
  }
348
531
  };
349
532
  function defineListener(options) {
350
533
  return new Listener(options);
351
534
  }
352
535
 
353
- // src/listener/ListenerManager.ts
536
+ // src/core/context/Context.ts
537
+ var Context = class {
538
+ canceled = false;
539
+ cancel() {
540
+ this.canceled = true;
541
+ }
542
+ };
354
543
  var ListenerManager = class extends BaseClientManager {
355
544
  listeners = [];
356
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
545
+ entries = new Collection();
357
546
  executors = /* @__PURE__ */ new WeakMap();
358
- async loadModules() {
359
- let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "listeners", "**/*.{ts,js}"), loads = (await glob(pattern, {
360
- cwd: process.cwd()
361
- })).map(async (file) => {
362
- try {
363
- let { default: listener } = await import(pathToFileURL(file).toString());
364
- if (!listener) {
365
- console.warn(`[Loader] File has no default export: ${file}`);
366
- return;
367
- }
368
- if (!(listener instanceof Listener)) {
369
- console.warn(`[Loader] Default export is not a Listener: ${file}`);
370
- return;
371
- }
372
- return this.add(listener), listener;
373
- } catch (error) {
374
- console.error(`An error occurred while trying to add listener for '${file}':`, error);
375
- }
376
- }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
377
- return console.log(`Loaded ${loaded.length} listener(s).`), loaded;
547
+ async loadModules(entryDir) {
548
+ let pattern = posix.join(posix.resolve(entryDir), "listeners", "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((l) => !!l);
549
+ return console.log(`[Loader] Loaded ${filtered.length}/${files.length} listener(s)`), filtered;
378
550
  }
551
+ /**
552
+ * Load the file and add the listener to the registry.
553
+ * @param path The path to the listener file.
554
+ * @returns The listener object if added successfully.
555
+ */
556
+ async load(path) {
557
+ let listener = (await import(pathToFileURL(path).href)).default;
558
+ if (!listener) {
559
+ console.warn(`[Loader] File has no default export: ${path}`);
560
+ return;
561
+ }
562
+ if (!(listener instanceof Listener)) {
563
+ console.warn(`[Loader] Default export is not a Listener: ${path}`);
564
+ return;
565
+ }
566
+ return this.add(listener), this.entries.set(path, listener), listener;
567
+ }
568
+ /**
569
+ * Unload the file and remove the listener from the registry.
570
+ * @param path The path to the listener file.
571
+ * @returns The listener object if unloaded successfully.
572
+ */
573
+ async unload(path) {
574
+ let listener = this.entries.get(path);
575
+ if (this.entries.delete(path), (await import('bakit/loader/register')).unload(path), !!listener)
576
+ return this.remove(listener)?.[0];
577
+ }
578
+ async reload(path) {
579
+ await this.unload(path);
580
+ let listener = await this.load(path);
581
+ if (listener)
582
+ return console.log(`[Loader] Reloaded listener '${listener.options.name}' at '${path}'`), listener;
583
+ }
584
+ /**
585
+ * Add a listener to the registry and create a listener for client.
586
+ * @param listener Listener to add.
587
+ */
379
588
  add(listener) {
380
589
  if (!(listener instanceof Listener))
381
590
  throw new Error("Invalid listener provided");
382
- let execute = (...args) => {
591
+ let { once, name } = listener.options, execute = (...args) => {
383
592
  listener.execute(new Context(), ...args);
384
593
  };
385
- this.listeners.push(listener), this.executors.set(listener, execute);
386
- let { once, name } = listener.options;
387
- this.client[once ? "once" : "on"](name, execute);
594
+ this.listeners.push(listener), this.executors.set(listener, execute), this.client[once ? "once" : "on"](name, execute);
388
595
  }
596
+ /**
597
+ * Remove a listener from the registry and client.
598
+ * @param target Listener name or object to remove.
599
+ * @returns The list of listener objects if removed successfully.
600
+ */
389
601
  remove(target) {
390
602
  let isMatched = (listener) => typeof target == "string" ? listener.options.name === target : listener === target, removed = [];
391
603
  return this.listeners = this.listeners.filter((listener) => {
@@ -396,21 +608,38 @@ var ListenerManager = class extends BaseClientManager {
396
608
  return execute && (this.client.removeListener(listener.options.name, execute), this.executors.delete(listener)), false;
397
609
  }), removed;
398
610
  }
611
+ /**
612
+ * Get a list of required intents for Bakit to run correctly.
613
+ * @returns Used intents.
614
+ */
615
+ getBaseIntents() {
616
+ return new IntentsBitField([GatewayIntentBits.Guilds]);
617
+ }
618
+ /**
619
+ * Get a list of needed intents based on registered listeners to receive needed events.
620
+ * @returns Used intents.
621
+ */
622
+ getNeededIntents() {
623
+ let result = this.getBaseIntents();
624
+ for (let listener of this.listeners) {
625
+ let eventName = listener.options.name, requiredIntents = EVENT_INTENT_MAPPING[eventName];
626
+ requiredIntents && result.add(requiredIntents);
627
+ }
628
+ return result;
629
+ }
399
630
  };
400
631
 
401
- // src/BakitClient.ts
402
- var BakitClient3 = class extends Client {
403
- managers;
404
- constructor(options) {
405
- super(options), this.managers = {
632
+ // src/core/client/BakitClient.ts
633
+ var BakitClient = class extends Client {
634
+ constructor(options, instance) {
635
+ super(options);
636
+ this.instance = instance;
637
+ this.managers = {
406
638
  commands: new CommandManager(this),
407
639
  listeners: new ListenerManager(this)
408
640
  };
409
641
  }
410
- async start(token) {
411
- let { commands, listeners } = this.managers;
412
- return await Promise.all([commands.loadModules(), listeners.loadModules()]), await this.login(token);
413
- }
642
+ managers;
414
643
  /**
415
644
  * Check if the client is connected to gateway successfully and finished initialization.
416
645
  */
@@ -443,25 +672,263 @@ var BakitClient3 = class extends Client {
443
672
  return `${this.constructor.name} {}`;
444
673
  }
445
674
  };
446
- var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z.object({
447
- name: z.string(),
448
- description: z.string().optional(),
449
- required: z.boolean().default(true)
450
- }), StringParamSchema = BaseParamSchema.extend({
451
- maxLength: z.number().min(1).optional(),
452
- minLength: z.number().min(1).optional()
453
- }), NumberParamSchema = BaseParamSchema.extend({
454
- maxValue: z.number().optional(),
455
- minValue: z.number().optional()
675
+ var BaseCommandContext = class extends Context {
676
+ constructor(source) {
677
+ super();
678
+ this.source = source;
679
+ }
680
+ get client() {
681
+ return this.source.client;
682
+ }
683
+ get channel() {
684
+ return this.source.channel;
685
+ }
686
+ get channelId() {
687
+ return this.source.channelId;
688
+ }
689
+ get guild() {
690
+ return this.source.guild;
691
+ }
692
+ get guildId() {
693
+ return this.source.guildId;
694
+ }
695
+ get member() {
696
+ return this.source.member;
697
+ }
698
+ inGuild() {
699
+ return !!this.guildId;
700
+ }
701
+ inCachedGuild() {
702
+ if (this.isChatInput())
703
+ return this.source.inCachedGuild();
704
+ if (this.isMessage())
705
+ return this.source.inGuild();
706
+ throw new Error("Invalid source");
707
+ }
708
+ get user() {
709
+ if (this.isChatInput())
710
+ return this.source.user;
711
+ if (this.isMessage())
712
+ return this.source.author;
713
+ throw new Error("Invalid source");
714
+ }
715
+ isChatInput() {
716
+ return this.source instanceof ChatInputCommandInteraction;
717
+ }
718
+ isMessage() {
719
+ return this.source instanceof Message;
720
+ }
721
+ }, ChatInputContext = class extends BaseCommandContext {
722
+ async send(options) {
723
+ typeof options == "string" && (options = { content: options });
724
+ let sendOptions = {
725
+ ...options,
726
+ withResponse: true
727
+ };
728
+ return this.source.deferred || this.source.replied ? await this.source.followUp(sendOptions) : (await this.source.reply(sendOptions)).resource?.message;
729
+ }
730
+ }, MessageContext = class extends BaseCommandContext {
731
+ async send(options) {
732
+ let { channel } = this;
733
+ if (!channel?.isSendable())
734
+ throw new Error("Invalid channel or channel is not sendable");
735
+ return await channel.send(options);
736
+ }
737
+ };
738
+ var ProjectConfigSchema = z4.object({
739
+ /**
740
+ * The gateway intents to use for the Discord client.
741
+ *
742
+ * - `auto` — automatically determine the required intents.
743
+ * - bigint — a raw bitfield value representing the combined intents.
744
+ * - array — a list of individual intent flags from `GatewayIntentBits`.
745
+ *
746
+ * @defaultvalue `auto`
747
+ */
748
+ intents: z4.union([z4.literal("auto"), z4.bigint(), z4.array(z4.enum(GatewayIntentBits))]).default("auto"),
749
+ /**
750
+ * Optional custom client options for Discord.js (excluding `intents`).
751
+ *
752
+ * These are passed directly to the `Client` constructor when initializing the bot.
753
+ *
754
+ * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
755
+ */
756
+ clientOptions: z4.custom().optional(),
757
+ prefixes: z4.array(z4.string()).default([]),
758
+ token: z4.string()
456
759
  });
760
+ function defineConfig(config) {
761
+ return config;
762
+ }
763
+ var _config;
764
+ async function loadConfig(cwd = process.cwd()) {
765
+ if (_config)
766
+ return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
767
+ let globPattern = `bakit.config.{${["ts", "js"].join(",")}}`, [configPath, other] = await glob(globPattern, {
768
+ cwd: cwd.replace(/\\/g, "/"),
769
+ // ensure the path uses `/` instead of `\` on Windows
770
+ absolute: true
771
+ });
772
+ if (!configPath)
773
+ throw new Error("Missing config file");
774
+ other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
775
+ let config = (await import(pathToFileURL(configPath).href)).default;
776
+ return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
777
+ }
778
+ function getConfig() {
779
+ if (!_config)
780
+ throw new Error("Project config is not loaded.");
781
+ return _config;
782
+ }
783
+ var messageCommandHandler = defineListener(Events.MessageCreate), chatInputCommandHandler = defineListener(Events.InteractionCreate), registerCommandsHandler = defineListener({
784
+ name: Events.ClientReady,
785
+ once: true
786
+ });
787
+ registerCommandsHandler.main(async (_, client) => {
788
+ 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);
789
+ if (cachedMeta && cachedMeta.hash === currentHash) {
790
+ let { timestamp, count } = cachedMeta, time = new Date(timestamp).toLocaleString();
791
+ console.log(`${count} command(s) are up to date (Last sync: ${time}). Skipping registration.`);
792
+ return;
793
+ }
794
+ try {
795
+ let result = await client.application.commands.set(payload);
796
+ cache.write(CACHE_KEY, {
797
+ hash: currentHash,
798
+ timestamp: Date.now(),
799
+ count: result.size
800
+ }), cache.write("commands/debug_dump.json", payload), console.log(`Registered ${result.size} application command(s).`);
801
+ } catch (error) {
802
+ console.error("Failed to register commands:", error);
803
+ }
804
+ });
805
+ messageCommandHandler.main(async (_, message) => {
806
+ let config = getConfig();
807
+ if (message.author.bot)
808
+ return;
809
+ let { content } = message, client = message.client, lowerContent = content.toLowerCase(), prefix = config.prefixes.find((p) => lowerContent.startsWith(p));
810
+ if (!prefix)
811
+ return;
812
+ let [name, ...args] = content.slice(prefix.length).trim().split(/\s+/g);
813
+ if (!name)
814
+ return;
815
+ let command = client.managers.commands.get(name);
816
+ if (!command)
817
+ return;
818
+ let context = new MessageContext(message), { params, quotes } = command.options, rawArgs = quotes ? tokenize(args.join(" ")) : args, resolvedArgs = [];
819
+ for (let i = 0; i < params.length; i++) {
820
+ let param = params[i], arg = rawArgs[i];
821
+ if (!param)
822
+ break;
823
+ let resolved = await param.resolve(context, arg);
824
+ resolvedArgs.push(resolved);
825
+ }
826
+ await command.execute(context, ...resolvedArgs);
827
+ });
828
+ chatInputCommandHandler.main(async (_, interaction) => {
829
+ if (!interaction.isChatInputCommand())
830
+ return;
831
+ let { commandName } = interaction, command = interaction.client.managers.commands.get(commandName);
832
+ if (!command)
833
+ return;
834
+ let context = new ChatInputContext(interaction), { params } = command.options, resolvedArgs = [];
835
+ for (let param of params) {
836
+ let resolved = await param.resolve(context);
837
+ resolvedArgs.push(resolved);
838
+ }
839
+ await command.execute(context, ...resolvedArgs);
840
+ });
841
+ var ProjectCacheManager = class {
842
+ rootDir;
843
+ constructor(root = process.cwd()) {
844
+ this.rootDir = join(root, ".bakit"), this.ensureRoot();
845
+ }
846
+ ensureRoot() {
847
+ existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
848
+ }
849
+ getHash(data) {
850
+ return createHash("sha256").update(JSON.stringify(data)).digest("hex");
851
+ }
852
+ async write(path, data) {
853
+ let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
854
+ await mkdir(dir, { recursive: true });
855
+ let content = typeof data == "string" ? data : JSON.stringify(data);
856
+ await writeFile(fullPath, content, "utf-8");
857
+ }
858
+ async read(path) {
859
+ let fullPath = join(this.rootDir, path);
860
+ try {
861
+ let content = await readFile(fullPath, "utf-8");
862
+ return JSON.parse(content);
863
+ } catch {
864
+ return null;
865
+ }
866
+ }
867
+ async clear() {
868
+ await rm(this.rootDir, { recursive: true, force: true });
869
+ }
870
+ clearSync() {
871
+ existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
872
+ }
873
+ };
874
+
875
+ // src/core/internal/Instance.ts
876
+ var Instance = class {
877
+ client;
878
+ cache;
879
+ constructor() {
880
+ this.cache = new ProjectCacheManager();
881
+ }
882
+ async start() {
883
+ await loadConfig();
884
+ let config = getConfig();
885
+ this.client = new BakitClient(
886
+ {
887
+ intents: [],
888
+ ...config.clientOptions
889
+ },
890
+ this
891
+ ), await this.loadModules(), this.initIntents(), await this.client.login(config.token), this.initProcess();
892
+ }
893
+ initProcess() {
894
+ process.on("message", (msg) => this.onProcessMessage(msg));
895
+ }
896
+ loadModules() {
897
+ let { managers } = this.client, { commands, listeners } = managers;
898
+ return listeners.add(chatInputCommandHandler), listeners.add(messageCommandHandler), listeners.add(registerCommandsHandler), Promise.all([commands.loadModules("src"), listeners.loadModules("src")]);
899
+ }
900
+ initIntents() {
901
+ let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
902
+ 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;
903
+ }
904
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
905
+ async onProcessMessage(message) {
906
+ let { type, url } = message;
907
+ if (!type.startsWith("hmr:"))
908
+ return;
909
+ let target = type.split(":")[1], { listeners, commands } = this.client.managers;
910
+ switch (target) {
911
+ case "listeners":
912
+ await listeners.reload(url);
913
+ break;
914
+ case "commands":
915
+ await commands.reload(url);
916
+ break;
917
+ }
918
+ }
919
+ };
920
+ function useApp() {
921
+ return new Instance();
922
+ }
457
923
 
458
- // src/command/param/Params.ts
924
+ // src/core/structures/param/Params.ts
459
925
  function createFactory(ctor) {
460
926
  return (...args) => new ctor(...args);
461
927
  }
462
928
  var Params = {
463
929
  string: createFactory(StringParam),
464
- number: createFactory(NumberParam)
930
+ number: createFactory(NumberParam),
931
+ user: createFactory(UserParam)
465
932
  };
466
933
 
467
- export { BakitClient3 as BakitClient, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Listener, ListenerManager, ListenerOptionsSchema, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectConfigSchema, StringParam, StringParamSchema, defineCommand, defineConfig, defineListener, getConfig, loadConfig };
934
+ export { ArgumentError, 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, ProjectCacheManager, ProjectConfigSchema, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };