bakit 2.0.0-alpha.2 → 2.0.0-alpha.20

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