bakit 2.0.0-alpha.3 → 2.0.0-alpha.30

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,12 +1,172 @@
1
- import { GatewayIntentBits, Events, Client, IntentsBitField, 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, Collection, Client, IntentsBitField, SlashCommandBuilder, SlashCommandStringOption, SlashCommandNumberOption, SlashCommandUserOption, ChatInputCommandInteraction, Message } from 'discord.js';
5
2
  import { inspect } from 'util';
6
- import { posix } from 'path';
3
+ import { pathToFileURL, fileURLToPath } from 'url';
4
+ import { join, resolve, relative, sep, dirname } from 'path';
5
+ import glob from 'tiny-glob';
6
+ import z2 from 'zod';
7
+ import { register } from 'module';
8
+ import { MessageChannel } from 'worker_threads';
9
+ import { randomUUID, createHash } from 'crypto';
10
+ import EventEmitter from 'events';
11
+ import { existsSync, mkdirSync, rmSync } from 'fs';
12
+ import { mkdir, writeFile, readFile, rm } from 'fs/promises';
13
+
14
+ var __defProp = Object.defineProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z2.object({
20
+ name: z2.string(),
21
+ description: z2.string().optional(),
22
+ required: z2.boolean().default(true)
23
+ }), StringParamSchema = BaseParamSchema.extend({
24
+ maxLength: z2.number().min(1).optional(),
25
+ minLength: z2.number().min(1).optional()
26
+ }), NumberParamSchema = BaseParamSchema.extend({
27
+ maxValue: z2.number().optional(),
28
+ minValue: z2.number().optional()
29
+ }), UserParamSchema = BaseParamSchema.extend({});
30
+ var INTENT_GROUPS = {
31
+ [GatewayIntentBits.Guilds]: [
32
+ Events.GuildCreate,
33
+ Events.GuildDelete,
34
+ Events.GuildUpdate,
35
+ Events.GuildUnavailable,
36
+ Events.GuildRoleCreate,
37
+ Events.GuildRoleDelete,
38
+ Events.GuildRoleUpdate,
39
+ Events.ChannelCreate,
40
+ Events.ChannelDelete,
41
+ Events.ChannelUpdate,
42
+ Events.ChannelPinsUpdate,
43
+ Events.ThreadCreate,
44
+ Events.ThreadDelete,
45
+ Events.ThreadUpdate,
46
+ Events.ThreadListSync,
47
+ Events.ThreadMemberUpdate,
48
+ Events.ThreadMembersUpdate,
49
+ Events.StageInstanceCreate,
50
+ Events.StageInstanceUpdate,
51
+ Events.StageInstanceDelete
52
+ ],
53
+ [GatewayIntentBits.GuildMembers]: [
54
+ Events.GuildMemberAdd,
55
+ Events.GuildMemberUpdate,
56
+ Events.GuildMemberRemove,
57
+ Events.ThreadMembersUpdate
58
+ ],
59
+ [GatewayIntentBits.GuildModeration]: [Events.GuildAuditLogEntryCreate, Events.GuildBanAdd, Events.GuildBanRemove],
60
+ [GatewayIntentBits.GuildExpressions]: [
61
+ Events.GuildEmojiCreate,
62
+ Events.GuildEmojiDelete,
63
+ Events.GuildEmojiUpdate,
64
+ Events.GuildStickerCreate,
65
+ Events.GuildStickerDelete,
66
+ Events.GuildStickerUpdate,
67
+ Events.GuildSoundboardSoundCreate,
68
+ Events.GuildSoundboardSoundUpdate,
69
+ Events.GuildSoundboardSoundDelete,
70
+ Events.GuildSoundboardSoundsUpdate
71
+ ],
72
+ [GatewayIntentBits.GuildIntegrations]: [Events.GuildIntegrationsUpdate],
73
+ [GatewayIntentBits.GuildWebhooks]: [Events.WebhooksUpdate],
74
+ [GatewayIntentBits.GuildInvites]: [Events.InviteCreate, Events.InviteDelete],
75
+ [GatewayIntentBits.GuildVoiceStates]: [Events.VoiceStateUpdate],
76
+ [GatewayIntentBits.GuildPresences]: [Events.PresenceUpdate],
77
+ [GatewayIntentBits.GuildMessages]: [
78
+ Events.MessageCreate,
79
+ Events.MessageUpdate,
80
+ Events.MessageDelete,
81
+ Events.MessageBulkDelete
82
+ ],
83
+ [GatewayIntentBits.GuildMessageReactions]: [
84
+ Events.MessageReactionAdd,
85
+ Events.MessageReactionRemove,
86
+ Events.MessageReactionRemoveAll,
87
+ Events.MessageReactionRemoveEmoji
88
+ ],
89
+ [GatewayIntentBits.GuildMessageTyping]: [Events.TypingStart],
90
+ [GatewayIntentBits.DirectMessages]: [
91
+ Events.MessageCreate,
92
+ Events.MessageUpdate,
93
+ Events.MessageDelete,
94
+ Events.ChannelPinsUpdate
95
+ ],
96
+ [GatewayIntentBits.DirectMessageReactions]: [
97
+ Events.MessageReactionAdd,
98
+ Events.MessageReactionRemove,
99
+ Events.MessageReactionRemoveAll,
100
+ Events.MessageReactionRemoveEmoji
101
+ ],
102
+ [GatewayIntentBits.DirectMessageTyping]: [Events.TypingStart],
103
+ [GatewayIntentBits.MessageContent]: [Events.MessageCreate, Events.MessageUpdate],
104
+ [GatewayIntentBits.GuildScheduledEvents]: [
105
+ Events.GuildScheduledEventCreate,
106
+ Events.GuildScheduledEventDelete,
107
+ Events.GuildScheduledEventUpdate,
108
+ Events.GuildScheduledEventUserAdd,
109
+ Events.GuildScheduledEventUserRemove
110
+ ],
111
+ [GatewayIntentBits.AutoModerationConfiguration]: [
112
+ Events.AutoModerationRuleCreate,
113
+ Events.AutoModerationRuleDelete,
114
+ Events.AutoModerationRuleUpdate
115
+ ],
116
+ [GatewayIntentBits.AutoModerationExecution]: [Events.AutoModerationActionExecution],
117
+ [GatewayIntentBits.GuildMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove],
118
+ [GatewayIntentBits.DirectMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove]
119
+ }, EVENT_INTENT_MAPPING = {};
120
+ for (let [intentStr, events] of Object.entries(INTENT_GROUPS)) {
121
+ let intent = Number(intentStr);
122
+ for (let event of events)
123
+ EVENT_INTENT_MAPPING[event] ??= [], EVENT_INTENT_MAPPING[event].includes(intent) || EVENT_INTENT_MAPPING[event].push(intent);
124
+ }
125
+
126
+ // src/lib/errors/BakitError.ts
127
+ var BakitError = class extends Error {
128
+ constructor(message) {
129
+ super(message), this.name = this.constructor.name, Object.setPrototypeOf(this, new.target.prototype);
130
+ }
131
+ };
132
+
133
+ // src/lib/errors/ArgumentError.ts
134
+ var ArgumentError = class extends BakitError {
135
+ constructor(target, reason) {
136
+ super(`Invalid argument for '${target}': ${reason}`);
137
+ this.target = target;
138
+ this.reason = reason;
139
+ }
140
+ };
7
141
 
8
- // src/config.ts
9
- var ProjectConfigSchema = z.object({
142
+ // src/lib/utils/string.ts
143
+ function tokenize(content) {
144
+ let args = [], current = "", quoteChar = null, isEscaped = false;
145
+ for (let i = 0; i < content.length; i++) {
146
+ let char = content[i];
147
+ if (char === void 0)
148
+ break;
149
+ if (isEscaped) {
150
+ current += char, isEscaped = false;
151
+ continue;
152
+ }
153
+ if (char === "\\") {
154
+ isEscaped = true;
155
+ continue;
156
+ }
157
+ quoteChar ? char === quoteChar ? quoteChar = null : current += char : char === '"' ? quoteChar = char : /\s/.test(char) ? current.length > 0 && (args.push(current), current = "") : current += char;
158
+ }
159
+ return current.length > 0 && args.push(current), args;
160
+ }
161
+ function extractSnowflakeId(input) {
162
+ if (!input) return null;
163
+ let mentionMatch = /^<@!?(\d{17,20})>$/.exec(input);
164
+ if (mentionMatch?.[1])
165
+ return mentionMatch[1];
166
+ let idMatch = /^(\d{17,20})$/.exec(input);
167
+ return idMatch?.[1] ? idMatch[1] : null;
168
+ }
169
+ var CONFIG_EXTENSIONS = ["ts", "js"], ProjectConfigSchema = z2.object({
10
170
  /**
11
171
  * The gateway intents to use for the Discord client.
12
172
  *
@@ -16,7 +176,7 @@ var ProjectConfigSchema = z.object({
16
176
  *
17
177
  * @defaultvalue `auto`
18
178
  */
19
- intents: z.union([z.literal("auto"), z.bigint(), z.array(z.enum(GatewayIntentBits))]).default("auto"),
179
+ intents: z2.union([z2.literal("auto"), z2.bigint(), z2.array(z2.enum(GatewayIntentBits))]).default("auto"),
20
180
  /**
21
181
  * Optional custom client options for Discord.js (excluding `intents`).
22
182
  *
@@ -24,15 +184,20 @@ var ProjectConfigSchema = z.object({
24
184
  *
25
185
  * @see {@link https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface}
26
186
  */
27
- clientOptions: z.custom().optional(),
187
+ clientOptions: z2.custom().optional(),
28
188
  /**
29
- * The path to the main project source directory.
30
- *
189
+ * Your bot prefixes to trigger the commands.
190
+ */
191
+ prefixes: z2.array(z2.string()).default([]),
192
+ /**
193
+ * Your Discord bot token.
194
+ */
195
+ token: z2.string(),
196
+ /**
197
+ * The main source directory.
31
198
  * @defaultvalue `src`
32
199
  */
33
- entryDir: z.string().default("src"),
34
- prefixes: z.array(z.string()).default([]),
35
- token: z.string()
200
+ entryDirectory: z2.string().default("src")
36
201
  });
37
202
  function defineConfig(config) {
38
203
  return config;
@@ -41,7 +206,7 @@ var _config;
41
206
  async function loadConfig(cwd = process.cwd()) {
42
207
  if (_config)
43
208
  return console.warn("loadConfig() was called more than once. This shouldn't happen."), _config;
44
- let globPattern = `bakit.config.{${["ts", "js"].join(",")}}`, [configPath, other] = await glob(globPattern, {
209
+ let globPattern = `bakit.config.{${CONFIG_EXTENSIONS.join(",")}}`, [configPath, other] = await glob(globPattern, {
45
210
  cwd: cwd.replace(/\\/g, "/"),
46
211
  // ensure the path uses `/` instead of `\` on Windows
47
212
  absolute: true
@@ -49,7 +214,7 @@ async function loadConfig(cwd = process.cwd()) {
49
214
  if (!configPath)
50
215
  throw new Error("Missing config file");
51
216
  other && console.warn(`Multiple config files found in ${cwd}. Using ${configPath}.`);
52
- let configFileURL = pathToFileURL(configPath).toString(), { default: config } = await import(configFileURL);
217
+ let config = (await import(pathToFileURL(configPath).href)).default;
53
218
  return _config = Object.freeze(await ProjectConfigSchema.parseAsync(config)), _config;
54
219
  }
55
220
  function getConfig() {
@@ -57,175 +222,262 @@ function getConfig() {
57
222
  throw new Error("Project config is not loaded.");
58
223
  return _config;
59
224
  }
225
+ function getTopLevelDirectory(path, entryDir) {
226
+ return relative(entryDir, path).split(sep)[0] ?? null;
227
+ }
228
+ function getEntryDirectory() {
229
+ return resolve(getConfig().entryDirectory);
230
+ }
231
+ function getEntryFile() {
232
+ return process.env.BAKIT_ENTRY_FILE;
233
+ }
60
234
 
61
- // src/base/lifecycle/Context.ts
62
- var Context = class {
63
- canceled = false;
64
- cancel() {
65
- this.canceled = true;
66
- }
67
- };
68
-
69
- // src/command/CommandContext.ts
70
- var BaseCommandContext = class extends Context {
71
- constructor(source) {
235
+ // src/lib/loader/loader.ts
236
+ var loader_exports = {};
237
+ __export(loader_exports, {
238
+ addHotReloader: () => addHotReloader,
239
+ containsEntryFile: () => containsEntryFile,
240
+ containsHotModule: () => containsHotModule,
241
+ getDependencyChain: () => getDependencyChain,
242
+ getImporters: () => getImporters,
243
+ getImports: () => getImports,
244
+ hotReloaders: () => hotReloaders,
245
+ importsAny: () => importsAny,
246
+ init: () => init,
247
+ isEntryFile: () => isEntryFile,
248
+ isImported: () => isImported,
249
+ isImportedBy: () => isImportedBy,
250
+ isInHotDirectory: () => isInHotDirectory,
251
+ restartProcess: () => restartProcess,
252
+ unload: () => unload
253
+ });
254
+ var RPC_RESPONSE_MARK = "$DONE:", RPC_RESPONSE_TIMEOUT = 5e3, RPC = class extends EventEmitter {
255
+ constructor(transport) {
72
256
  super();
73
- this.source = source;
74
- }
75
- get client() {
76
- return this.source.client;
77
- }
78
- get channel() {
79
- return this.source.channel;
80
- }
81
- get channelId() {
82
- return this.source.channelId;
83
- }
84
- get guild() {
85
- return this.source.guild;
86
- }
87
- get guildId() {
88
- return this.source.guildId;
89
- }
90
- get member() {
91
- return this.source.member;
92
- }
93
- inGuild() {
94
- return !!this.guildId;
95
- }
96
- inCachedGuild() {
97
- if (this.isChatInput())
98
- return this.source.inCachedGuild();
99
- if (this.isMessage())
100
- return this.source.inGuild();
101
- throw new Error("Invalid source");
102
- }
103
- get user() {
104
- if (this.isChatInput())
105
- return this.source.user;
106
- if (this.isMessage())
107
- return this.source.author;
108
- throw new Error("Invalid source");
109
- }
110
- isChatInput() {
111
- return this.source instanceof ChatInputCommandInteraction;
112
- }
113
- isMessage() {
114
- return this.source instanceof Message;
115
- }
116
- }, ChatInputContext = class extends BaseCommandContext {
117
- async send(options) {
118
- typeof options == "string" && (options = { content: options });
119
- let sendOptions = {
120
- ...options,
121
- withResponse: true
122
- };
123
- return this.source.deferred || this.source.replied ? await this.source.followUp(sendOptions) : (await this.source.reply(sendOptions)).resource?.message;
124
- }
125
- }, MessageContext = class extends BaseCommandContext {
126
- async send(options) {
127
- let { channel } = this;
128
- if (!channel?.isSendable())
129
- throw new Error("Invalid channel or channel is not sendable");
130
- return await channel.send(options);
131
- }
132
- };
133
- var LifecycleManager = class {
134
- constructor(id) {
135
- this.id = id;
257
+ this.transport = transport;
258
+ this.transport.on("message", (message) => this.onMessage(message));
136
259
  }
137
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
138
- hooks = {
139
- MAIN: new Collection(),
140
- PRE: new Collection(),
141
- POST: new Collection(),
142
- ERROR: new Collection()
143
- };
144
- getName(name) {
145
- return `${this.id}:${name}`;
260
+ requests = /* @__PURE__ */ new Map();
261
+ postMessage(message) {
262
+ let { transport } = this;
263
+ "send" in transport ? transport.send(message) : "postMessage" in transport ? transport.postMessage(message) : console.warn(`${transport.constructor.name} doesn't support IPC`);
146
264
  }
147
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
148
- setHook(name, state, callback, order = 1 /* Last */) {
149
- let currentHooks = this.hooks[state], key = this.getName(name);
150
- if (currentHooks.has(key) && console.warn(`Overriding duplicate hook '${key}' for state '${state}'`), order === 1 /* Last */)
151
- currentHooks.set(key, callback);
152
- else {
153
- let existingEntries = [...currentHooks.entries()].filter(([k]) => k !== key);
154
- currentHooks.clear(), currentHooks.set(key, callback);
155
- for (let [k, v] of existingEntries)
156
- currentHooks.set(k, v);
265
+ onMessage(message) {
266
+ if (message.id.startsWith(RPC_RESPONSE_MARK)) {
267
+ this.handleResponseMessage(message);
268
+ return;
269
+ }
270
+ if ("type" in message) {
271
+ this.handleRequestMessage(message);
272
+ return;
157
273
  }
158
- return this;
159
- }
160
- main(callback) {
161
- return this.setHook("main", "MAIN" /* Main */, callback);
162
274
  }
163
- pre(callback) {
164
- return this.setHook("pre", "PRE" /* Pre */, callback);
275
+ handleResponseMessage(message) {
276
+ let id = message.id.slice(RPC_RESPONSE_MARK.length), request = this.requests.get(id);
277
+ if (!request)
278
+ return;
279
+ let { reject, resolve: resolve6, timeout } = request;
280
+ this.requests.delete(id), clearTimeout(timeout), "data" in message ? resolve6(message.data) : reject(new Error(message.error));
165
281
  }
166
- post(callback) {
167
- return this.setHook("post", "POST" /* Post */, callback);
282
+ handleRequestMessage(message) {
283
+ this.emit("message", message), this.emit(message.type, message.id, message.data);
168
284
  }
169
- error(callback) {
170
- return this.setHook("error", "ERROR" /* Error */, callback);
285
+ send(type, data = {}, id = randomUUID()) {
286
+ let message = {
287
+ id,
288
+ type,
289
+ data
290
+ };
291
+ this.postMessage(message);
171
292
  }
172
- async execute(context, ...args) {
173
- let pipeline = [
174
- ...this.hooks.PRE.values(),
175
- ...this.hooks.MAIN.values(),
176
- ...this.hooks.POST.values()
177
- ], error;
178
- for (let hook of pipeline) {
179
- if (context.canceled)
180
- break;
181
- try {
182
- await hook(context, ...args);
183
- } catch (e) {
184
- error = e;
185
- break;
186
- }
187
- }
188
- if (!error)
189
- return;
190
- if (!this.hooks.ERROR.size)
191
- throw error;
192
- for (let [key, callback] of this.hooks.ERROR.entries()) {
193
- if (context.canceled)
194
- break;
195
- try {
196
- await callback(context, error, ...args);
197
- } catch (innerError) {
198
- console.error(`[Lifecycle] Error handler for '${key}' failed:`, innerError);
199
- }
200
- }
293
+ success(id, data) {
294
+ let message = {
295
+ id: `${RPC_RESPONSE_MARK}${id}`,
296
+ data
297
+ };
298
+ this.postMessage(message);
201
299
  }
202
- };
203
-
204
- // src/errors/BakitError.ts
205
- var BakitError = class extends Error {
206
- constructor(message) {
207
- super(message), this.name = this.constructor.name, Object.setPrototypeOf(this, new.target.prototype);
300
+ error(id, error) {
301
+ let message = {
302
+ id: `${RPC_RESPONSE_MARK}${id}`,
303
+ error
304
+ };
305
+ this.postMessage(message);
306
+ }
307
+ request(type, data, id = randomUUID()) {
308
+ return new Promise((resolve6, reject) => {
309
+ let timeout = setTimeout(() => {
310
+ this.requests.delete(id) && reject(new Error("Request timed out"));
311
+ }, RPC_RESPONSE_TIMEOUT);
312
+ this.requests.set(id, {
313
+ resolve: resolve6,
314
+ reject,
315
+ timeout
316
+ }), this.send(type, data, id);
317
+ });
208
318
  }
209
319
  };
210
320
 
211
- // src/errors/ArgumentError.ts
212
- var ArgumentError = class extends BakitError {
213
- constructor(target, reason) {
214
- super(`Invalid argument for '${target}': ${reason}`);
215
- this.target = target;
216
- this.reason = reason;
217
- }
218
- };
321
+ // src/lib/loader/loader.ts
322
+ var hooksRPC, processRPC, hotReloaders = new Collection(), reverseDependencyGraph = new Collection(), forwardDependencyGraph = new Collection();
323
+ function init() {
324
+ initProcess(), initHooks();
325
+ }
326
+ function initProcess() {
327
+ processRPC = new RPC(process), processRPC.on("fileChange", (_, path) => onFileChange(path)), processRPC.on("fileRemove", (_, path) => onFileRemove(path));
328
+ }
329
+ function initHooks() {
330
+ let { port1, port2 } = new MessageChannel(), hookPath = new URL("./hooks.js", import.meta.url).href;
331
+ register(hookPath, import.meta.url, {
332
+ data: { port: port1 },
333
+ transferList: [port1]
334
+ }), hooksRPC = new RPC(port2), hooksRPC.on("dependencyAdd", (_, data) => onDependencyAdd(data)), port2.unref();
335
+ }
336
+ function addHotReloader(reloader) {
337
+ hotReloaders.set(reloader.entryDirectory, reloader);
338
+ }
339
+ function unload(path) {
340
+ if (!hooksRPC)
341
+ throw new Error("Loader isn't initialized");
342
+ return hooksRPC.request("unload", resolve(path));
343
+ }
344
+ function getImporters(path, createNew = false) {
345
+ path = resolve(path);
346
+ let entry = reverseDependencyGraph.get(path);
347
+ return createNew && !entry && (entry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, entry)), entry;
348
+ }
349
+ function getImports(path) {
350
+ path = resolve(path);
351
+ let imports = [];
352
+ for (let [target, importers] of reverseDependencyGraph)
353
+ importers.has(path) && imports.push(target);
354
+ return imports;
355
+ }
356
+ function getDependencyChain(path) {
357
+ path = resolve(path);
358
+ let queue = [path], visited = /* @__PURE__ */ new Set();
359
+ for (; queue.length > 0; ) {
360
+ let current = queue.shift();
361
+ if (!current || visited.has(current) || (visited.add(current), current.includes("/node_modules/")))
362
+ continue;
363
+ let parents = getImporters(current);
364
+ if (!parents)
365
+ continue;
366
+ for (let parent of parents)
367
+ visited.has(parent) || queue.push(parent);
368
+ let children = getImports(current);
369
+ for (let child of children)
370
+ visited.has(child) || importsAny(child, visited) && queue.push(child);
371
+ }
372
+ return Array.from(visited);
373
+ }
374
+ function importsAny(path, targets) {
375
+ return getImports(path).some((imp) => targets.has(imp));
376
+ }
377
+ function isImported(path) {
378
+ return !!getImporters(path)?.size;
379
+ }
380
+ function isImportedBy(path, matcher) {
381
+ return getDependencyChain(path).slice(1).some((p) => {
382
+ let isMatch = false;
383
+ return typeof matcher == "string" && (isMatch = resolve(matcher) === p), typeof matcher == "function" && (isMatch = matcher(p)), matcher instanceof RegExp && (isMatch = matcher.test(p)), isMatch;
384
+ });
385
+ }
386
+ function onDependencyAdd(data) {
387
+ let { url, parentURL } = data, path = fileURLToPath(url), parentPath = fileURLToPath(parentURL);
388
+ if (parentPath.includes("/node_modules/"))
389
+ return;
390
+ let reverseEntry = reverseDependencyGraph.get(path);
391
+ reverseEntry || (reverseEntry = /* @__PURE__ */ new Set(), reverseDependencyGraph.set(path, reverseEntry)), reverseEntry.add(parentPath);
392
+ let forwardEntry = forwardDependencyGraph.get(parentPath);
393
+ forwardEntry || (forwardEntry = /* @__PURE__ */ new Set(), forwardDependencyGraph.set(parentPath, forwardEntry)), forwardEntry.add(path);
394
+ }
395
+ function isInHotDirectory(path) {
396
+ let sourceRoot = getEntryDirectory();
397
+ if (!path.startsWith(sourceRoot))
398
+ return false;
399
+ let topLevelDir = getTopLevelDirectory(path, sourceRoot);
400
+ if (!topLevelDir)
401
+ return;
402
+ let entryDirectory = join(sourceRoot, topLevelDir);
403
+ return hotReloaders.some((m) => m.entryDirectory === entryDirectory);
404
+ }
405
+ function isEntryFile(path) {
406
+ return path === getEntryFile();
407
+ }
408
+ function containsEntryFile(chain) {
409
+ return chain.some((x) => isEntryFile(x));
410
+ }
411
+ function containsHotModule(chain) {
412
+ return chain.some((x) => isInHotDirectory(x));
413
+ }
414
+ function restartProcess() {
415
+ processRPC?.send("restart");
416
+ }
417
+ async function unloadModule(path, reload = false) {
418
+ let topLevel = getTopLevelDirectory(path, getEntryDirectory());
419
+ if (!topLevel)
420
+ return;
421
+ let directory = resolve(getEntryDirectory(), topLevel), reloader = hotReloaders.get(directory);
422
+ if (!reloader) {
423
+ await unload(path);
424
+ return;
425
+ }
426
+ reloader[reload ? "reload" : "unload"](path);
427
+ }
428
+ async function onFileRemove(path) {
429
+ if (isEntryFile(path)) {
430
+ restartProcess();
431
+ return;
432
+ }
433
+ if (!isImported(path))
434
+ return;
435
+ let chain = getDependencyChain(path);
436
+ if (containsEntryFile(chain)) {
437
+ restartProcess();
438
+ return;
439
+ }
440
+ if (containsHotModule(chain))
441
+ for (let path2 of chain.reverse())
442
+ await unloadModule(path2);
443
+ }
444
+ async function onFileChange(path) {
445
+ if (isEntryFile(path)) {
446
+ restartProcess();
447
+ return;
448
+ }
449
+ if (!isImported(path))
450
+ return;
451
+ let chain = getDependencyChain(path);
452
+ if (containsEntryFile(chain)) {
453
+ restartProcess();
454
+ return;
455
+ }
456
+ if (containsHotModule(chain))
457
+ for (let path2 of chain.toReversed())
458
+ await unloadModule(path2, true);
459
+ }
219
460
 
220
- // src/command/param/Param.ts
461
+ // src/core/structures/param/Param.ts
221
462
  var BaseParam = class {
222
- options;
223
- constructor(options) {
224
- this.options = { ...options, required: options.required ?? true };
463
+ constructor(options, schema) {
464
+ this.schema = schema;
465
+ let parsed = schema.parse({
466
+ ...options,
467
+ description: options.description ?? options.name
468
+ });
469
+ this.options = parsed;
225
470
  }
471
+ options;
226
472
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
473
  setOption(key, value) {
228
- return value === null ? delete this.options[key] : this.options[key] = value, this;
474
+ if (value === null)
475
+ return delete this.options[key], this;
476
+ let fieldValidator = this.schema.shape[key];
477
+ if (!fieldValidator)
478
+ return this.options[key] = value, this;
479
+ let parsedValue = fieldValidator.parse(value);
480
+ return this.options[key] = parsedValue, this;
229
481
  }
230
482
  name(value) {
231
483
  return this.setOption("name", value);
@@ -239,8 +491,15 @@ var BaseParam = class {
239
491
  async resolve(context, value) {
240
492
  if (context.isChatInput())
241
493
  return await this.resolveChatInput(context);
242
- if (context.isMessage())
494
+ if (context.isMessage()) {
495
+ let { required, name } = this.options;
496
+ if (value === void 0) {
497
+ if (required)
498
+ throw new ArgumentError(name, "is required");
499
+ return null;
500
+ }
243
501
  return await this.resolveMessage(context, value);
502
+ }
244
503
  throw new Error("Invalid context type provided");
245
504
  }
246
505
  /**
@@ -251,18 +510,13 @@ var BaseParam = class {
251
510
  }
252
511
  }, StringParam = class extends BaseParam {
253
512
  constructor(options) {
254
- super(BaseParam.getOptions(options));
513
+ super(BaseParam.getOptions(options), StringParamSchema);
255
514
  }
256
515
  required(value) {
257
516
  return super.required(value);
258
517
  }
259
518
  resolveMessage(_context, value) {
260
- let { required, minLength, maxLength, name } = this.options;
261
- if (value === void 0) {
262
- if (required)
263
- throw new ArgumentError(name, "is required");
264
- return null;
265
- }
519
+ let { minLength, maxLength, name } = this.options;
266
520
  if (minLength && value.length < minLength)
267
521
  throw new ArgumentError(name, `must be at least ${minLength} chars long`);
268
522
  if (maxLength && value.length > maxLength)
@@ -289,19 +543,13 @@ var BaseParam = class {
289
543
  }
290
544
  }, NumberParam = class extends BaseParam {
291
545
  constructor(options) {
292
- super(BaseParam.getOptions(options));
546
+ super(BaseParam.getOptions(options), NumberParamSchema);
293
547
  }
294
548
  required(value) {
295
549
  return super.required(value);
296
550
  }
297
- resolveMessage(ctx, value) {
298
- let { required, minValue, maxValue, name } = this.options;
299
- if (value === void 0) {
300
- if (required)
301
- throw new ArgumentError(name, "is required");
302
- return null;
303
- }
304
- let num = Number(value);
551
+ resolveMessage(_context, value) {
552
+ let { minValue, maxValue, name } = this.options, num = Number(value);
305
553
  if (isNaN(num))
306
554
  throw new ArgumentError(name, "must be a number");
307
555
  if (minValue !== void 0 && num < minValue)
@@ -328,18 +576,120 @@ var BaseParam = class {
328
576
  max(value) {
329
577
  return this.setOption("maxValue", value);
330
578
  }
579
+ }, UserParam = class extends BaseParam {
580
+ constructor(options) {
581
+ super(BaseParam.getOptions(options), UserParamSchema);
582
+ }
583
+ required(value) {
584
+ return super.required(value);
585
+ }
586
+ async resolveMessage(context, value) {
587
+ let id = extractSnowflakeId(value);
588
+ if (!id)
589
+ return null;
590
+ let { users } = context.client;
591
+ return await users.fetch(id).catch(() => null);
592
+ }
593
+ resolveChatInput(context) {
594
+ let { name, required } = this.options;
595
+ return context.source.options.getUser(name, required);
596
+ }
597
+ };
598
+ 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 {
599
+ constructor(id) {
600
+ this.id = id;
601
+ }
602
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
603
+ hooks = {
604
+ MAIN: new Collection(),
605
+ PRE: new Collection(),
606
+ POST: new Collection(),
607
+ ERROR: new Collection()
608
+ };
609
+ getName(name) {
610
+ return `${this.id}:${name}`;
611
+ }
612
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
613
+ setHook(name, state, callback, order = 1 /* Last */) {
614
+ let currentHooks = this.hooks[state], key = this.getName(name);
615
+ if (currentHooks.has(key) && console.warn(`Overriding duplicate hook '${key}' for state '${state}'`), order === 1 /* Last */)
616
+ currentHooks.set(key, callback);
617
+ else {
618
+ let existingEntries = [...currentHooks.entries()].filter(([k]) => k !== key);
619
+ currentHooks.clear(), currentHooks.set(key, callback);
620
+ for (let [k, v] of existingEntries)
621
+ currentHooks.set(k, v);
622
+ }
623
+ return this;
624
+ }
625
+ main(callback) {
626
+ return this.setHook("main", "MAIN" /* Main */, callback);
627
+ }
628
+ pre(callback) {
629
+ return this.setHook("pre", "PRE" /* Pre */, callback);
630
+ }
631
+ post(callback) {
632
+ return this.setHook("post", "POST" /* Post */, callback);
633
+ }
634
+ error(callback) {
635
+ return this.setHook("error", "ERROR" /* Error */, callback);
636
+ }
637
+ async execute(context, ...args) {
638
+ let pipeline = [
639
+ ...this.hooks.PRE.values(),
640
+ ...this.hooks.MAIN.values(),
641
+ ...this.hooks.POST.values()
642
+ ], error;
643
+ for (let hook of pipeline) {
644
+ if (context.canceled)
645
+ break;
646
+ try {
647
+ await hook(context, ...args);
648
+ } catch (e) {
649
+ error = e;
650
+ break;
651
+ }
652
+ }
653
+ if (!error)
654
+ return;
655
+ if (!this.hooks.ERROR.size)
656
+ throw error;
657
+ for (let [key, callback] of this.hooks.ERROR.entries()) {
658
+ if (context.canceled)
659
+ break;
660
+ try {
661
+ await callback(context, error, ...args);
662
+ } catch (innerError) {
663
+ console.error(`[Lifecycle] Error handler for '${key}' failed:`, innerError);
664
+ }
665
+ }
666
+ }
331
667
  };
332
668
 
333
- // src/command/Command.ts
334
- var CommandOptionsSchema = z.object({
335
- name: z.string(),
336
- description: z.string().min(1).max(100).optional(),
337
- params: z.array(z.instanceof(BaseParam)).default([]),
338
- quotes: z.boolean().default(true)
669
+ // src/core/structures/Command.ts
670
+ function validateParamsOrder(params) {
671
+ let seenOptional = false;
672
+ for (let param of params)
673
+ if (param.options.required) {
674
+ if (seenOptional)
675
+ return false;
676
+ } else
677
+ seenOptional = true;
678
+ return true;
679
+ }
680
+ var CommandOptionsSchema = z2.object({
681
+ name: z2.string().readonly(),
682
+ description: z2.string().min(1).max(100).optional().readonly(),
683
+ nsfw: z2.boolean().default(false).readonly(),
684
+ params: z2.array(z2.instanceof(BaseParam)).default([]).readonly(),
685
+ quotes: z2.boolean().default(true).readonly()
339
686
  }).transform((data) => ({
340
687
  ...data,
341
688
  description: data.description ?? `Command ${data.name}`
342
- })), Command = class extends LifecycleManager {
689
+ })).refine(({ params }) => validateParamsOrder(params), {
690
+ path: ["params"],
691
+ error: "Required params must be placed before optional params"
692
+ }), Command = class extends LifecycleManager {
343
693
  constructor(options) {
344
694
  let _options = CommandOptionsSchema.parse(typeof options == "string" ? { name: options } : options);
345
695
  super(`command:${_options.name}`), this.options = _options, this.setHook("syntaxError", "ERROR" /* Error */, async (ctx, error, ...args) => {
@@ -349,42 +699,100 @@ var CommandOptionsSchema = z.object({
349
699
  async handleSyntaxError(context, error, _args) {
350
700
  error instanceof BakitError && await context.send(error.message);
351
701
  }
702
+ toSlashCommandJSON() {
703
+ let { name, description, nsfw, params } = this.options, builder = new SlashCommandBuilder().setName(name).setDescription(description).setNSFW(nsfw);
704
+ return this.initSlashCommandOptions(builder, params), builder.toJSON();
705
+ }
706
+ initSlashCommandOptions(builder, params) {
707
+ for (let param of params)
708
+ this.initSlashCommandOption(builder, param);
709
+ }
710
+ initSlashCommandOption(builder, param) {
711
+ let initOption = (builder2) => {
712
+ let { name, description, required } = param.options;
713
+ return builder2.setName(name).setDescription(description).setRequired(required);
714
+ };
715
+ if (param instanceof StringParam) {
716
+ let { maxLength, minLength } = param.options, option = initOption(new SlashCommandStringOption());
717
+ maxLength && option.setMaxLength(maxLength), minLength && option.setMinLength(minLength), builder.addStringOption(option);
718
+ return;
719
+ }
720
+ if (param instanceof NumberParam) {
721
+ let { maxValue, minValue } = param.options, option = initOption(new SlashCommandNumberOption());
722
+ maxValue && option.setMaxValue(maxValue), minValue && option.setMinValue(minValue), builder.addNumberOption(option);
723
+ return;
724
+ }
725
+ if (param instanceof UserParam) {
726
+ let option = initOption(new SlashCommandUserOption());
727
+ builder.addUserOption(option);
728
+ return;
729
+ }
730
+ }
352
731
  };
353
732
  function defineCommand(options) {
354
733
  return new Command(options);
355
734
  }
735
+ var HotReloadable2 = class {
736
+ constructor(entryDirectory) {
737
+ this.entryDirectory = entryDirectory;
738
+ entryDirectory = resolve(entryDirectory);
739
+ }
740
+ unloadFile(path) {
741
+ return loader_exports.unload(path);
742
+ }
743
+ };
356
744
 
357
- // src/base/BaseClientManager.ts
358
- var BaseClientManager = class {
745
+ // src/core/managers/CommandManager.ts
746
+ var CommandManager = class extends HotReloadable2 {
359
747
  constructor(client) {
748
+ super(join(getEntryDirectory(), "commands"));
360
749
  this.client = client;
361
750
  }
362
- };
363
-
364
- // src/command/CommandManager.ts
365
- var CommandManager = class extends BaseClientManager {
366
751
  commands = new Collection();
752
+ entries = new Collection();
367
753
  async loadModules() {
368
- let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "commands", "**/*.{ts,js}"), loads = (await glob(pattern, {
369
- cwd: process.cwd()
370
- })).map(async (file) => {
371
- try {
372
- let { default: command } = await import(pathToFileURL(file).toString());
373
- if (!command) {
374
- console.warn(`[Loader] File has no default export: ${file}`);
375
- return;
376
- }
377
- if (!(command instanceof Command)) {
378
- console.warn(`[Loader] Default export is not a Command: ${file}`);
379
- return;
380
- }
381
- return this.add(command), command;
382
- } catch (error) {
383
- console.error(`An error occurred while trying to add command for '${file}':`, error);
384
- }
385
- }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
386
- return console.log(`Loaded ${loaded.length} command(s).`), loaded;
754
+ let pattern = join(this.entryDirectory, "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((c) => !!c);
755
+ return console.log(`[Loader] Loaded ${filtered.length}/${files.length} command(s)`), filtered;
756
+ }
757
+ /**
758
+ * Load the file and add the command to the registry.
759
+ * @param path The path to the command file.
760
+ * @returns The command object if added successfully.
761
+ */
762
+ async load(path) {
763
+ path = resolve(path);
764
+ let command = (await import(pathToFileURL(path).href)).default;
765
+ if (!command) {
766
+ console.warn(`[Loader] File has no default export: ${path}`);
767
+ return;
768
+ }
769
+ if (!(command instanceof Command)) {
770
+ console.warn(`[Loader] Default export is not a Command: ${path}`);
771
+ return;
772
+ }
773
+ return this.add(command), this.entries.set(path, command), command;
387
774
  }
775
+ /**
776
+ * Unload the file and remove the command from the registry.
777
+ * @param path The path to the command file.
778
+ * @returns The command object if unloaded successfully.
779
+ */
780
+ async unload(path) {
781
+ path = resolve(path);
782
+ let command = this.entries.get(path);
783
+ if (this.entries.delete(path), await this.unloadFile(path), !!command)
784
+ return this.remove(command);
785
+ }
786
+ async reload(path) {
787
+ path = resolve(path), await this.unload(path);
788
+ let command = await this.load(path);
789
+ if (command)
790
+ return console.log(`[Loader] Reloaded command '${command.options.name}' at '${path}'`), command;
791
+ }
792
+ /**
793
+ * Add a command to the registry.
794
+ * @param command Command to add.
795
+ */
388
796
  add(command) {
389
797
  if (!(command instanceof Command))
390
798
  throw new Error("Invalid command provided");
@@ -395,18 +803,30 @@ var CommandManager = class extends BaseClientManager {
395
803
  }
396
804
  this.commands.set(name, command);
397
805
  }
806
+ /**
807
+ * Remove a command from the registry.
808
+ * @param target Command name or object to remove.
809
+ * @returns The command object if removed successfully.
810
+ */
398
811
  remove(target) {
812
+ if (typeof target != "string" && !(target instanceof Command))
813
+ return;
399
814
  let name = typeof target == "string" ? target : target.options.name, existing = this.commands.get(name);
400
815
  if (existing)
401
816
  return this.commands.delete(name), existing;
402
817
  }
818
+ /**
819
+ * Get a command using its name.
820
+ * @param name The command to get.
821
+ * @returns The command object.
822
+ */
403
823
  get(name) {
404
824
  return this.commands.get(name);
405
825
  }
406
826
  };
407
- var ListenerOptionsSchema = z3.object({
408
- name: z3.enum(Events),
409
- once: z3.boolean().default(false)
827
+ var ListenerOptionsSchema = z2.object({
828
+ name: z2.enum(Events),
829
+ once: z2.boolean().default(false)
410
830
  }), Listener = class extends LifecycleManager {
411
831
  options;
412
832
  constructor(options) {
@@ -417,138 +837,80 @@ var ListenerOptionsSchema = z3.object({
417
837
  function defineListener(options) {
418
838
  return new Listener(options);
419
839
  }
420
- var INTENT_GROUPS = {
421
- [GatewayIntentBits.Guilds]: [
422
- Events.GuildCreate,
423
- Events.GuildDelete,
424
- Events.GuildUpdate,
425
- Events.GuildUnavailable,
426
- Events.GuildRoleCreate,
427
- Events.GuildRoleDelete,
428
- Events.GuildRoleUpdate,
429
- Events.ChannelCreate,
430
- Events.ChannelDelete,
431
- Events.ChannelUpdate,
432
- Events.ChannelPinsUpdate,
433
- Events.ThreadCreate,
434
- Events.ThreadDelete,
435
- Events.ThreadUpdate,
436
- Events.ThreadListSync,
437
- Events.ThreadMemberUpdate,
438
- Events.ThreadMembersUpdate,
439
- Events.StageInstanceCreate,
440
- Events.StageInstanceUpdate,
441
- Events.StageInstanceDelete
442
- ],
443
- [GatewayIntentBits.GuildMembers]: [
444
- Events.GuildMemberAdd,
445
- Events.GuildMemberUpdate,
446
- Events.GuildMemberRemove,
447
- Events.ThreadMembersUpdate
448
- ],
449
- [GatewayIntentBits.GuildModeration]: [Events.GuildAuditLogEntryCreate, Events.GuildBanAdd, Events.GuildBanRemove],
450
- [GatewayIntentBits.GuildExpressions]: [
451
- Events.GuildEmojiCreate,
452
- Events.GuildEmojiDelete,
453
- Events.GuildEmojiUpdate,
454
- Events.GuildStickerCreate,
455
- Events.GuildStickerDelete,
456
- Events.GuildStickerUpdate,
457
- Events.GuildSoundboardSoundCreate,
458
- Events.GuildSoundboardSoundUpdate,
459
- Events.GuildSoundboardSoundDelete,
460
- Events.GuildSoundboardSoundsUpdate
461
- ],
462
- [GatewayIntentBits.GuildIntegrations]: [Events.GuildIntegrationsUpdate],
463
- [GatewayIntentBits.GuildWebhooks]: [Events.WebhooksUpdate],
464
- [GatewayIntentBits.GuildInvites]: [Events.InviteCreate, Events.InviteDelete],
465
- [GatewayIntentBits.GuildVoiceStates]: [Events.VoiceStateUpdate],
466
- [GatewayIntentBits.GuildPresences]: [Events.PresenceUpdate],
467
- [GatewayIntentBits.GuildMessages]: [
468
- Events.MessageCreate,
469
- Events.MessageUpdate,
470
- Events.MessageDelete,
471
- Events.MessageBulkDelete
472
- ],
473
- [GatewayIntentBits.GuildMessageReactions]: [
474
- Events.MessageReactionAdd,
475
- Events.MessageReactionRemove,
476
- Events.MessageReactionRemoveAll,
477
- Events.MessageReactionRemoveEmoji
478
- ],
479
- [GatewayIntentBits.GuildMessageTyping]: [Events.TypingStart],
480
- [GatewayIntentBits.DirectMessages]: [
481
- Events.MessageCreate,
482
- Events.MessageUpdate,
483
- Events.MessageDelete,
484
- Events.ChannelPinsUpdate
485
- ],
486
- [GatewayIntentBits.DirectMessageReactions]: [
487
- Events.MessageReactionAdd,
488
- Events.MessageReactionRemove,
489
- Events.MessageReactionRemoveAll,
490
- Events.MessageReactionRemoveEmoji
491
- ],
492
- [GatewayIntentBits.DirectMessageTyping]: [Events.TypingStart],
493
- [GatewayIntentBits.MessageContent]: [Events.MessageCreate, Events.MessageUpdate],
494
- [GatewayIntentBits.GuildScheduledEvents]: [
495
- Events.GuildScheduledEventCreate,
496
- Events.GuildScheduledEventDelete,
497
- Events.GuildScheduledEventUpdate,
498
- Events.GuildScheduledEventUserAdd,
499
- Events.GuildScheduledEventUserRemove
500
- ],
501
- [GatewayIntentBits.AutoModerationConfiguration]: [
502
- Events.AutoModerationRuleCreate,
503
- Events.AutoModerationRuleDelete,
504
- Events.AutoModerationRuleUpdate
505
- ],
506
- [GatewayIntentBits.AutoModerationExecution]: [Events.AutoModerationActionExecution],
507
- [GatewayIntentBits.GuildMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove],
508
- [GatewayIntentBits.DirectMessagePolls]: [Events.MessagePollVoteAdd, Events.MessagePollVoteRemove]
509
- }, EVENT_INTENT_MAPPING = {};
510
- for (let [intentStr, events] of Object.entries(INTENT_GROUPS)) {
511
- let intent = Number(intentStr);
512
- for (let event of events)
513
- EVENT_INTENT_MAPPING[event] ??= [], EVENT_INTENT_MAPPING[event].includes(intent) || EVENT_INTENT_MAPPING[event].push(intent);
514
- }
515
840
 
516
- // src/listener/ListenerManager.ts
517
- var ListenerManager = class extends BaseClientManager {
841
+ // src/core/context/Context.ts
842
+ var Context = class {
843
+ canceled = false;
844
+ cancel() {
845
+ this.canceled = true;
846
+ }
847
+ };
848
+
849
+ // src/core/managers/ListenerManager.ts
850
+ var ListenerManager = class extends HotReloadable2 {
851
+ constructor(client) {
852
+ super(join(getEntryDirectory(), "listeners"));
853
+ this.client = client;
854
+ }
518
855
  listeners = [];
519
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
856
+ entries = new Collection();
520
857
  executors = /* @__PURE__ */ new WeakMap();
521
858
  async loadModules() {
522
- let entryDir = posix.resolve(getConfig().entryDir), pattern = posix.join(entryDir, "listeners", "**/*.{ts,js}"), loads = (await glob(pattern, {
523
- cwd: process.cwd()
524
- })).map(async (file) => {
525
- try {
526
- let { default: listener } = await import(pathToFileURL(file).toString());
527
- if (!listener) {
528
- console.warn(`[Loader] File has no default export: ${file}`);
529
- return;
530
- }
531
- if (!(listener instanceof Listener)) {
532
- console.warn(`[Loader] Default export is not a Listener: ${file}`);
533
- return;
534
- }
535
- return this.add(listener), listener;
536
- } catch (error) {
537
- console.error(`An error occurred while trying to add listener for '${file}':`, error);
538
- }
539
- }), loaded = (await Promise.all(loads)).filter((x) => x !== void 0);
540
- return console.log(`Loaded ${loaded.length} listener(s).`), loaded;
859
+ let pattern = join(this.entryDirectory, "**/*.{ts,js}"), files = await glob(pattern, { cwd: process.cwd(), absolute: true }), filtered = (await Promise.all(files.map((file) => this.load(file)))).filter((l) => !!l);
860
+ return console.log(`[Loader] Loaded ${filtered.length}/${files.length} listener(s)`), filtered;
861
+ }
862
+ /**
863
+ * Load the file and add the listener to the registry.
864
+ * @param path The path to the listener file.
865
+ * @returns The listener object if added successfully.
866
+ */
867
+ async load(path) {
868
+ path = resolve(path);
869
+ let listener = (await import(pathToFileURL(path).href)).default;
870
+ if (!listener) {
871
+ console.warn(`[Loader] File has no default export: ${path}`);
872
+ return;
873
+ }
874
+ if (!(listener instanceof Listener)) {
875
+ console.warn(`[Loader] Default export is not a Listener: ${path}`);
876
+ return;
877
+ }
878
+ return this.add(listener), this.entries.set(path, listener), listener;
879
+ }
880
+ /**
881
+ * Unload the file and remove the listener from the registry.
882
+ * @param path The path to the listener file.
883
+ * @returns The listener object if unloaded successfully.
884
+ */
885
+ async unload(path) {
886
+ path = resolve(path);
887
+ let listener = this.entries.get(path);
888
+ if (this.entries.delete(path), await this.unloadFile(path), !!listener)
889
+ return this.remove(listener)?.[0];
890
+ }
891
+ async reload(path) {
892
+ path = resolve(path), await this.unload(path);
893
+ let listener = await this.load(path);
894
+ if (listener)
895
+ return console.log(`[Loader] Reloaded listener '${listener.options.name}' at '${path}'`), listener;
541
896
  }
897
+ /**
898
+ * Add a listener to the registry and create a listener for client.
899
+ * @param listener Listener to add.
900
+ */
542
901
  add(listener) {
543
902
  if (!(listener instanceof Listener))
544
903
  throw new Error("Invalid listener provided");
545
- let execute = (...args) => {
904
+ let { once, name } = listener.options, execute = (...args) => {
546
905
  listener.execute(new Context(), ...args);
547
906
  };
548
- this.listeners.push(listener), this.executors.set(listener, execute);
549
- let { once, name } = listener.options;
550
- this.client[once ? "once" : "on"](name, execute);
907
+ this.listeners.push(listener), this.executors.set(listener, execute), this.client[once ? "once" : "on"](name, execute);
551
908
  }
909
+ /**
910
+ * Remove a listener from the registry and client.
911
+ * @param target Listener name or object to remove.
912
+ * @returns The list of listener objects if removed successfully.
913
+ */
552
914
  remove(target) {
553
915
  let isMatched = (listener) => typeof target == "string" ? listener.options.name === target : listener === target, removed = [];
554
916
  return this.listeners = this.listeners.filter((listener) => {
@@ -559,9 +921,17 @@ var ListenerManager = class extends BaseClientManager {
559
921
  return execute && (this.client.removeListener(listener.options.name, execute), this.executors.delete(listener)), false;
560
922
  }), removed;
561
923
  }
924
+ /**
925
+ * Get a list of required intents for Bakit to run correctly.
926
+ * @returns Used intents.
927
+ */
562
928
  getBaseIntents() {
563
929
  return new IntentsBitField([GatewayIntentBits.Guilds]);
564
930
  }
931
+ /**
932
+ * Get a list of needed intents based on registered listeners to receive needed events.
933
+ * @returns Used intents.
934
+ */
565
935
  getNeededIntents() {
566
936
  let result = this.getBaseIntents();
567
937
  for (let listener of this.listeners) {
@@ -572,15 +942,17 @@ var ListenerManager = class extends BaseClientManager {
572
942
  }
573
943
  };
574
944
 
575
- // src/BakitClient.ts
576
- var BakitClient3 = class extends Client {
577
- managers;
578
- constructor(options) {
579
- super(options), this.managers = {
945
+ // src/core/client/BakitClient.ts
946
+ var BakitClient = class extends Client {
947
+ constructor(options, instance) {
948
+ super(options);
949
+ this.instance = instance;
950
+ this.managers = {
580
951
  commands: new CommandManager(this),
581
952
  listeners: new ListenerManager(this)
582
953
  };
583
954
  }
955
+ managers;
584
956
  /**
585
957
  * Check if the client is connected to gateway successfully and finished initialization.
586
958
  */
@@ -613,25 +985,213 @@ var BakitClient3 = class extends Client {
613
985
  return `${this.constructor.name} {}`;
614
986
  }
615
987
  };
616
- var ParamUserType = /* @__PURE__ */ ((ParamUserType2) => (ParamUserType2.Bot = "bot", ParamUserType2.Normal = "normal", ParamUserType2.Any = "any", ParamUserType2))(ParamUserType || {}), BaseParamSchema = z.object({
617
- name: z.string(),
618
- description: z.string().optional(),
619
- required: z.boolean().default(true)
620
- }), StringParamSchema = BaseParamSchema.extend({
621
- maxLength: z.number().min(1).optional(),
622
- minLength: z.number().min(1).optional()
623
- }), NumberParamSchema = BaseParamSchema.extend({
624
- maxValue: z.number().optional(),
625
- minValue: z.number().optional()
988
+
989
+ // src/core/managers/BaseClientManager.ts
990
+ var BaseClientManager = class {
991
+ constructor(client) {
992
+ this.client = client;
993
+ }
994
+ };
995
+ var BaseCommandContext = class extends Context {
996
+ constructor(source) {
997
+ super();
998
+ this.source = source;
999
+ }
1000
+ get client() {
1001
+ return this.source.client;
1002
+ }
1003
+ get channel() {
1004
+ return this.source.channel;
1005
+ }
1006
+ get channelId() {
1007
+ return this.source.channelId;
1008
+ }
1009
+ get guild() {
1010
+ return this.source.guild;
1011
+ }
1012
+ get guildId() {
1013
+ return this.source.guildId;
1014
+ }
1015
+ get member() {
1016
+ return this.source.member;
1017
+ }
1018
+ inGuild() {
1019
+ return !!this.guildId;
1020
+ }
1021
+ inCachedGuild() {
1022
+ if (this.isChatInput())
1023
+ return this.source.inCachedGuild();
1024
+ if (this.isMessage())
1025
+ return this.source.inGuild();
1026
+ throw new Error("Invalid source");
1027
+ }
1028
+ get user() {
1029
+ if (this.isChatInput())
1030
+ return this.source.user;
1031
+ if (this.isMessage())
1032
+ return this.source.author;
1033
+ throw new Error("Invalid source");
1034
+ }
1035
+ isChatInput() {
1036
+ return this.source instanceof ChatInputCommandInteraction;
1037
+ }
1038
+ isMessage() {
1039
+ return this.source instanceof Message;
1040
+ }
1041
+ }, ChatInputContext = class extends BaseCommandContext {
1042
+ async send(options) {
1043
+ typeof options == "string" && (options = { content: options });
1044
+ let sendOptions = {
1045
+ ...options,
1046
+ withResponse: true
1047
+ };
1048
+ return this.source.deferred || this.source.replied ? await this.source.followUp(sendOptions) : (await this.source.reply(sendOptions)).resource?.message;
1049
+ }
1050
+ }, MessageContext = class extends BaseCommandContext {
1051
+ async send(options) {
1052
+ let { channel } = this;
1053
+ if (!channel?.isSendable())
1054
+ throw new Error("Invalid channel or channel is not sendable");
1055
+ return await channel.send(options);
1056
+ }
1057
+ };
1058
+ var ProjectCacheManager = class {
1059
+ rootDir;
1060
+ constructor(root = process.cwd()) {
1061
+ this.rootDir = join(root, ".bakit"), this.ensureRoot();
1062
+ }
1063
+ ensureRoot() {
1064
+ existsSync(this.rootDir) || mkdirSync(this.rootDir, { recursive: true });
1065
+ }
1066
+ getHash(data) {
1067
+ return createHash("sha256").update(JSON.stringify(data)).digest("hex");
1068
+ }
1069
+ async write(path, data) {
1070
+ let fullPath = join(this.rootDir, path), dir = dirname(fullPath);
1071
+ await mkdir(dir, { recursive: true });
1072
+ let content = typeof data == "string" ? data : JSON.stringify(data);
1073
+ await writeFile(fullPath, content, "utf-8");
1074
+ }
1075
+ async read(path) {
1076
+ let fullPath = join(this.rootDir, path);
1077
+ try {
1078
+ let content = await readFile(fullPath, "utf-8");
1079
+ return JSON.parse(content);
1080
+ } catch {
1081
+ return null;
1082
+ }
1083
+ }
1084
+ async clear() {
1085
+ await rm(this.rootDir, { recursive: true, force: true });
1086
+ }
1087
+ clearSync() {
1088
+ existsSync(this.rootDir) && rmSync(this.rootDir, { recursive: true, force: true });
1089
+ }
1090
+ };
1091
+ var messageCommandHandler = defineListener(Events.MessageCreate), chatInputCommandHandler = defineListener(Events.InteractionCreate), registerCommandsHandler = defineListener({
1092
+ name: Events.ClientReady,
1093
+ once: true
626
1094
  });
1095
+ registerCommandsHandler.main(async (_, client) => {
1096
+ 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);
1097
+ if (cachedMeta && cachedMeta.hash === currentHash) {
1098
+ let { timestamp, count } = cachedMeta, time = new Date(timestamp).toLocaleString();
1099
+ console.log(`${count} command(s) are up to date (Last sync: ${time}). Skipping registration.`);
1100
+ return;
1101
+ }
1102
+ try {
1103
+ let result = await client.application.commands.set(payload);
1104
+ cache.write(CACHE_KEY, {
1105
+ hash: currentHash,
1106
+ timestamp: Date.now(),
1107
+ count: result.size
1108
+ }), cache.write("commands/debug_dump.json", payload), console.log(`Registered ${result.size} application command(s).`);
1109
+ } catch (error) {
1110
+ console.error("Failed to register commands:", error);
1111
+ }
1112
+ });
1113
+ messageCommandHandler.main(async (_, message) => {
1114
+ let config = getConfig();
1115
+ if (message.author.bot)
1116
+ return;
1117
+ let { content } = message, client = message.client, lowerContent = content.toLowerCase(), prefix = config.prefixes.find((p) => lowerContent.startsWith(p));
1118
+ if (!prefix)
1119
+ return;
1120
+ let [name, ...args] = content.slice(prefix.length).trim().split(/\s+/g);
1121
+ if (!name)
1122
+ return;
1123
+ let command = client.managers.commands.get(name);
1124
+ if (!command)
1125
+ return;
1126
+ let context = new MessageContext(message), { params, quotes } = command.options, rawArgs = quotes ? tokenize(args.join(" ")) : args, resolvedArgs = [];
1127
+ for (let i = 0; i < params.length; i++) {
1128
+ let param = params[i], arg = rawArgs[i];
1129
+ if (!param)
1130
+ break;
1131
+ let resolved = await param.resolve(context, arg);
1132
+ resolvedArgs.push(resolved);
1133
+ }
1134
+ await command.execute(context, ...resolvedArgs);
1135
+ });
1136
+ chatInputCommandHandler.main(async (_, interaction) => {
1137
+ if (!interaction.isChatInputCommand())
1138
+ return;
1139
+ let { commandName } = interaction, command = interaction.client.managers.commands.get(commandName);
1140
+ if (!command)
1141
+ return;
1142
+ let context = new ChatInputContext(interaction), { params } = command.options, resolvedArgs = [];
1143
+ for (let param of params) {
1144
+ let resolved = await param.resolve(context);
1145
+ resolvedArgs.push(resolved);
1146
+ }
1147
+ await command.execute(context, ...resolvedArgs);
1148
+ });
1149
+
1150
+ // src/core/internal/Instance.ts
1151
+ var Instance = class {
1152
+ client;
1153
+ cache;
1154
+ constructor() {
1155
+ this.cache = new ProjectCacheManager();
1156
+ }
1157
+ async start() {
1158
+ await loadConfig();
1159
+ let config = getConfig();
1160
+ this.client = new BakitClient(
1161
+ {
1162
+ intents: [],
1163
+ ...config.clientOptions
1164
+ },
1165
+ this
1166
+ ), await this.loadModules(), this.initIntents(), await this.client.login(config.token), this.initProcess();
1167
+ }
1168
+ initProcess() {
1169
+ process.on("SIGINT", () => this.shutdown()), process.on("SIGTERM", () => this.shutdown());
1170
+ }
1171
+ loadModules() {
1172
+ let { managers } = this.client, { commands, listeners } = managers;
1173
+ return listeners.add(chatInputCommandHandler), listeners.add(messageCommandHandler), listeners.add(registerCommandsHandler), addHotReloader(commands), addHotReloader(listeners), Promise.all([commands.loadModules(), listeners.loadModules()]);
1174
+ }
1175
+ initIntents() {
1176
+ let config = getConfig(), { options, managers } = this.client, { listeners } = managers, intents;
1177
+ 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;
1178
+ }
1179
+ async shutdown() {
1180
+ this.client && await this.client.destroy().catch(() => null), process.exit(0);
1181
+ }
1182
+ };
1183
+ function useApp() {
1184
+ return new Instance();
1185
+ }
627
1186
 
628
- // src/command/param/Params.ts
1187
+ // src/core/structures/param/Params.ts
629
1188
  function createFactory(ctor) {
630
1189
  return (...args) => new ctor(...args);
631
1190
  }
632
1191
  var Params = {
633
1192
  string: createFactory(StringParam),
634
- number: createFactory(NumberParam)
1193
+ number: createFactory(NumberParam),
1194
+ user: createFactory(UserParam)
635
1195
  };
636
1196
 
637
- 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 };
1197
+ export { ArgumentError, BakitClient, BakitError, BaseClientManager, BaseCommandContext, BaseParam, BaseParamSchema, ChatInputContext, Command, CommandManager, CommandOptionsSchema, Context, EVENT_INTENT_MAPPING, HookOrder, HookState, HotReloadable2 as HotReloadable, Instance, LifecycleManager, Listener, ListenerManager, ListenerOptionsSchema, loader_exports as Loader, MessageContext, NumberParam, NumberParamSchema, ParamUserType, Params, ProjectCacheManager, ProjectConfigSchema, RPC, RPC_RESPONSE_MARK, RPC_RESPONSE_TIMEOUT, StringParam, StringParamSchema, UserParam, UserParamSchema, chatInputCommandHandler, defineCommand, defineConfig, defineListener, extractSnowflakeId, getConfig, getEntryDirectory, getEntryFile, getTopLevelDirectory, loadConfig, messageCommandHandler, registerCommandsHandler, tokenize, useApp, validateParamsOrder };