@scotthamilton77/discord-bot-lib 1.0.2 → 1.1.0

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/README.md CHANGED
@@ -10,7 +10,7 @@ TypeScript library for managing Discord bot identities, messaging, and multi-bot
10
10
  ## Features
11
11
 
12
12
  - **Factory construction** -- `Bot.fromConfig()` connects, verifies guilds, and returns a ready bot in one call
13
- - **Unified receive API** -- event-driven callback (`onMessage`) and async iterable (`messages()`) with full routing metadata (`isDM`, `isMention`, `isReply`, `replyTo`)
13
+ - **Unified receive API** -- event-driven callback (`onMessage`) and async iterable (`messages()`) with full routing metadata (`isDM`, `isMention`, `isReply`, `replyTo`), including reliable DM detection for cold-start/partial channels
14
14
  - **Multi-bot management** -- `ConnectorManager` aggregates messages across bots with lifecycle events
15
15
  - **Guided onboarding** -- `BotOnboarding` walks through token validation, server invitation, and permission verification
16
16
  - **Message chunking** -- automatically splits messages at paragraph/line/word boundaries to stay within Discord's 2000-char limit
@@ -55,6 +55,40 @@ bot.on("error", (err) => console.error("Bot error:", err.message));
55
55
  await bot.disconnect();
56
56
  ```
57
57
 
58
+ ## Typing Events
59
+
60
+ Listen for when users start typing. **Requires explicit intents** -- `GuildMessageTyping` and/or `DirectMessageTyping` are not included in the default intents, and Discord silently drops the events without them.
61
+
62
+ ```typescript
63
+ import { Bot } from "@scotthamilton77/discord-bot-lib";
64
+ import { GatewayIntentBits } from "discord.js";
65
+
66
+ const bot = await Bot.fromConfig({
67
+ id: "typing-bot",
68
+ name: "TypingBot",
69
+ token: process.env.DISCORD_TOKEN!,
70
+ intents: [
71
+ GatewayIntentBits.Guilds,
72
+ GatewayIntentBits.GuildMessages,
73
+ GatewayIntentBits.MessageContent,
74
+ GatewayIntentBits.DirectMessages,
75
+ GatewayIntentBits.GuildMessageTyping, // required for guild typing
76
+ GatewayIntentBits.DirectMessageTyping, // required for DM typing
77
+ ],
78
+ });
79
+
80
+ // Returns an unsubscribe function (same pattern as onMessage)
81
+ const unsubscribe = bot.onTypingStart((event) => {
82
+ console.log(`User ${event.userId} started typing in ${event.channelId}`);
83
+ if (event.guildId === null) {
84
+ console.log("(this is a DM)");
85
+ }
86
+ });
87
+
88
+ // Throws if bot was not configured with typing intents
89
+ // bot.onTypingStart(handler) → Error: "onTypingStart requires GuildMessageTyping and/or DirectMessageTyping intents"
90
+ ```
91
+
58
92
  ## Async Iterables
59
93
 
60
94
  All messages are also available as an `AsyncIterable`, useful for sequential processing with `for await`:
@@ -183,6 +217,10 @@ await bot.react(channelId, messageId, "\ud83d\udc4d");
183
217
 
184
218
  // Edit a message
185
219
  await bot.editMessage(channelId, messageId, "Updated content");
220
+
221
+ // Typing indicator (shows "Bot is typing..." for ~10s)
222
+ await bot.sendTyping(channelId);
223
+ await bot.sendDMTyping(userId);
186
224
  ```
187
225
 
188
226
  ## Fetching History
@@ -270,6 +308,7 @@ const { buffer, filename, contentType } = await downloadAttachment(attachment);
270
308
  | `BotStatus` | `"unregistered" \| "configuring" \| "connecting" \| "verifying" \| "ready" \| "disconnected" \| "failed"` |
271
309
  | `MessageEvent` | Incoming message with routing metadata (`isDM`, `isMention`, `isReply`, `replyTo`), author, content, channelId, mentions, and `raw` discord.js Message. |
272
310
  | `SentMessage` | Tracked outgoing message with messageId, channelId, timestamp, and `raw` escape hatch. |
311
+ | `TypingEvent` | Typing indicator with `channelId`, `userId`, `startedAt`, `guildId` (null for DMs), and `raw` discord.js Typing. |
273
312
  | `MessageContent` | `string \| { content?: string; embeds?: unknown[]; files?: FileAttachment[] }` |
274
313
  | `FetchedMessage` | Lightweight message from `fetchMessages()` / `fetchHistory()`. |
275
314
  | `FetchHistoryOptions` | Discriminated union: `{ after: string } \| { before: string }` with optional `limit`. |
package/dist/index.cjs CHANGED
@@ -164,14 +164,18 @@ var Bot = class _Bot {
164
164
  sentMessages;
165
165
  _connectedAt = null;
166
166
  messageHandlers = [];
167
+ typingHandlers = [];
167
168
  errorHandlers = [];
168
169
  _includeBotMessages;
169
- constructor(id, name, client, sentMessageCacheSize, includeBotMessages) {
170
+ resolvedIntents;
171
+ typingListenerAttached = false;
172
+ constructor(id, name, client, sentMessageCacheSize, includeBotMessages, resolvedIntents) {
170
173
  this.id = id;
171
174
  this.name = name;
172
175
  this.client = client;
173
176
  this.sentMessages = new import_discord.LimitedCollection({ maxSize: sentMessageCacheSize });
174
177
  this._includeBotMessages = includeBotMessages;
178
+ this.resolvedIntents = resolvedIntents;
175
179
  this._status = "connecting";
176
180
  }
177
181
  get includeBotMessages() {
@@ -198,11 +202,12 @@ var Bot = class _Bot {
198
202
  );
199
203
  }
200
204
  const includeBotMessages = config.includeBotMessages ?? false;
205
+ const intents = config.intents ?? [...DEFAULT_INTENTS];
201
206
  const client = new import_discord.Client({
202
- intents: config.intents ?? [...DEFAULT_INTENTS],
207
+ intents,
203
208
  partials: config.partials ?? [...DEFAULT_PARTIALS]
204
209
  });
205
- const bot = new _Bot(config.id, config.name, client, cacheSize, includeBotMessages);
210
+ const bot = new _Bot(config.id, config.name, client, cacheSize, includeBotMessages, intents);
206
211
  await bot.connect(config.token);
207
212
  bot.verify();
208
213
  bot.setupEventHandlers();
@@ -261,6 +266,31 @@ var Bot = class _Bot {
261
266
  );
262
267
  }
263
268
  }
269
+ async sendTyping(channelId) {
270
+ const channel = await this.fetchTextChannel(channelId);
271
+ const typingChannel = channel;
272
+ if (typeof typingChannel.sendTyping !== "function") {
273
+ throw new Error(`Channel ${channelId} does not support typing indicators`);
274
+ }
275
+ try {
276
+ await typingChannel.sendTyping();
277
+ } catch (error) {
278
+ throw new Error(
279
+ `Cannot send typing to channel ${channelId}: ${error instanceof Error ? error.message : String(error)}`
280
+ );
281
+ }
282
+ }
283
+ async sendDMTyping(userId) {
284
+ try {
285
+ const user = await this.client.users.fetch(userId);
286
+ const dm = await user.createDM();
287
+ await dm.sendTyping();
288
+ } catch (error) {
289
+ throw new Error(
290
+ `Cannot send typing to user ${userId}: ${error instanceof Error ? error.message : String(error)}`
291
+ );
292
+ }
293
+ }
264
294
  async fetchMessages(channelId, limit) {
265
295
  const channel = await this.fetchTextChannel(channelId);
266
296
  const clamped = Math.max(1, Math.min(limit ?? 20, 100));
@@ -324,6 +354,36 @@ var Bot = class _Bot {
324
354
  if (index !== -1) this.messageHandlers.splice(index, 1);
325
355
  };
326
356
  }
357
+ onTypingStart(handler) {
358
+ const hasGuildTyping = this.resolvedIntents.includes(import_discord.GatewayIntentBits.GuildMessageTyping);
359
+ const hasDMTyping = this.resolvedIntents.includes(import_discord.GatewayIntentBits.DirectMessageTyping);
360
+ if (!hasGuildTyping && !hasDMTyping) {
361
+ throw new Error(
362
+ "onTypingStart requires GuildMessageTyping and/or DirectMessageTyping intents. Add them to the intents array in BotConfig."
363
+ );
364
+ }
365
+ if (!this.typingListenerAttached) {
366
+ this.typingListenerAttached = true;
367
+ this.client.on(import_discord.Events.TypingStart, (typing) => {
368
+ try {
369
+ const event = this.toTypingEvent(typing);
370
+ for (const h of this.typingHandlers) {
371
+ h(event);
372
+ }
373
+ } catch (error) {
374
+ this.emitError(error);
375
+ }
376
+ });
377
+ }
378
+ this.typingHandlers.push(handler);
379
+ let removed = false;
380
+ return () => {
381
+ if (removed) return;
382
+ removed = true;
383
+ const index = this.typingHandlers.indexOf(handler);
384
+ if (index !== -1) this.typingHandlers.splice(index, 1);
385
+ };
386
+ }
327
387
  // --- Receiving (async iterable) ---
328
388
  messages(options) {
329
389
  const cleanup = {};
@@ -358,6 +418,8 @@ var Bot = class _Bot {
358
418
  this.client.removeAllListeners();
359
419
  await this.client.destroy();
360
420
  this.messageHandlers.length = 0;
421
+ this.typingHandlers.length = 0;
422
+ this.typingListenerAttached = false;
361
423
  this.errorHandlers.length = 0;
362
424
  this._status = "disconnected";
363
425
  this._connectedAt = null;
@@ -427,6 +489,7 @@ var Bot = class _Bot {
427
489
  setupEventHandlers() {
428
490
  this.client.on(import_discord.Events.MessageCreate, (message) => {
429
491
  try {
492
+ if (!message.author) return;
430
493
  if (message.author.id === this.client.user?.id) return;
431
494
  if (message.author.bot && !this._includeBotMessages) return;
432
495
  const event = this.toMessageEvent(message);
@@ -437,9 +500,34 @@ var Bot = class _Bot {
437
500
  this.emitError(error);
438
501
  }
439
502
  });
503
+ this.client.on("raw", (packet) => {
504
+ if (packet.t !== "MESSAGE_CREATE") return;
505
+ if (packet.d.guild_id) return;
506
+ const channelId = packet.d.channel_id;
507
+ const messageId = packet.d.id;
508
+ if (!channelId || !messageId) return;
509
+ if (this.client.channels.cache.has(channelId)) return;
510
+ void this.handleUncachedDM(channelId, messageId);
511
+ });
512
+ }
513
+ async handleUncachedDM(channelId, messageId) {
514
+ try {
515
+ const channel = await this.client.channels.fetch(channelId);
516
+ if (!channel?.isTextBased()) return;
517
+ const message = await channel.messages.fetch(messageId);
518
+ if (!message.author) return;
519
+ if (message.author.id === this.client.user?.id) return;
520
+ if (message.author.bot && !this._includeBotMessages) return;
521
+ const event = this.toMessageEvent(message);
522
+ for (const handler of this.messageHandlers) {
523
+ handler(event);
524
+ }
525
+ } catch (error) {
526
+ this.emitError(error);
527
+ }
440
528
  }
441
529
  toMessageEvent(message) {
442
- const isDM = message.channel.type === import_discord.ChannelType.DM;
530
+ const isDM = message.guildId === null;
443
531
  const botUser = this.client.user;
444
532
  const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);
445
533
  let isReply = false;
@@ -470,6 +558,15 @@ var Bot = class _Bot {
470
558
  replyTo
471
559
  };
472
560
  }
561
+ toTypingEvent(typing) {
562
+ return {
563
+ channelId: typing.channel.id,
564
+ userId: typing.user.id,
565
+ startedAt: typing.startedAt,
566
+ guildId: typing.guild?.id ?? null,
567
+ raw: typing
568
+ };
569
+ }
473
570
  async sendPayloads(content, dispatcher) {
474
571
  const files = typeof content !== "string" && content.files?.length ? await validateAndBuildAttachments(content.files) : void 0;
475
572
  const payloads = toChunkedPayloads(content, files);
@@ -676,6 +773,7 @@ var ConnectorManager = class {
676
773
  botMap = /* @__PURE__ */ new Map();
677
774
  botUnsubscribers = /* @__PURE__ */ new Map();
678
775
  messageHandlers = [];
776
+ typingHandlers = [];
679
777
  lifecycleHandlers = /* @__PURE__ */ new Map();
680
778
  addBot(bot) {
681
779
  if (this.botMap.has(bot.id)) {
@@ -695,6 +793,9 @@ var ConnectorManager = class {
695
793
  this.emitLifecycle("botError", bot, error);
696
794
  })
697
795
  );
796
+ if (this.typingHandlers.length > 0) {
797
+ this.subscribeTyping(bot, unsubs);
798
+ }
698
799
  this.botUnsubscribers.set(bot.id, unsubs);
699
800
  }
700
801
  getBot(id) {
@@ -721,11 +822,35 @@ var ConnectorManager = class {
721
822
  onMessage(handler) {
722
823
  this.messageHandlers.push(handler);
723
824
  }
825
+ onTypingStart(handler) {
826
+ const isFirst = this.typingHandlers.length === 0;
827
+ this.typingHandlers.push(handler);
828
+ if (isFirst) {
829
+ for (const [id, bot] of this.botMap) {
830
+ const unsubs = this.botUnsubscribers.get(id) ?? [];
831
+ this.subscribeTyping(bot, unsubs);
832
+ }
833
+ }
834
+ }
724
835
  on(event, callback) {
725
836
  const handlers = this.lifecycleHandlers.get(event) ?? [];
726
837
  handlers.push(callback);
727
838
  this.lifecycleHandlers.set(event, handlers);
728
839
  }
840
+ subscribeTyping(bot, unsubs) {
841
+ try {
842
+ unsubs.push(
843
+ bot.onTypingStart((event) => {
844
+ for (const handler of this.typingHandlers) {
845
+ handler(event, bot);
846
+ }
847
+ })
848
+ );
849
+ } catch (error) {
850
+ const isIntentError = error instanceof Error && error.message.startsWith("onTypingStart requires");
851
+ if (!isIntentError) throw error;
852
+ }
853
+ }
729
854
  emitLifecycle(event, ...args) {
730
855
  const handlers = this.lifecycleHandlers.get(event);
731
856
  if (handlers) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/bot.ts","../src/event-buffer.ts","../src/message-utils.ts","../src/attachment-utils.ts","../src/onboarding.ts","../src/connector-manager.ts"],"sourcesContent":["// discord-bot-lib — public API\n\nexport { Bot, DEFAULT_SENT_MESSAGE_CACHE_SIZE } from \"./bot.js\";\nexport { BotOnboarding } from \"./onboarding.js\";\nexport { ConnectorManager } from \"./connector-manager.js\";\nexport { EventBuffer } from \"./event-buffer.js\";\nexport {\n chunkMessage,\n DISCORD_MAX_MESSAGE_LENGTH,\n} from \"./message-utils.js\";\nexport {\n MAX_ATTACHMENT_BYTES,\n validateAttachmentSize,\n sanitizeAttachmentName,\n downloadAttachment,\n} from \"./attachment-utils.js\";\n\nexport type {\n AttachmentLike,\n DownloadableAttachment,\n DownloadedAttachment,\n} from \"./attachment-utils.js\";\n\nexport type {\n Author,\n BotConfig,\n BotStatus,\n BotStateAdapter,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n FileAttachment,\n FetchedMessage,\n FetchHistoryOptions,\n OnboardingStep,\n OnboardingStepStatus,\n StepResult,\n ManagerEvents,\n} from \"./types.js\";\n","import { Client, ChannelType, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder, type Message } from \"discord.js\";\nimport { stat } from \"node:fs/promises\";\nimport type {\n BotConfig,\n BotStatus,\n FetchedMessage,\n FetchHistoryOptions,\n FileAttachment,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n} from \"./types.js\";\nimport { EventBuffer } from \"./event-buffer.js\";\nimport { chunkMessage } from \"./message-utils.js\";\nimport { validateAttachmentSize, type DownloadableAttachment } from \"./attachment-utils.js\";\n\nexport const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;\n\nexport const DEFAULT_INTENTS = [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n] as const;\n\nexport const DEFAULT_PARTIALS = [Partials.Channel, Partials.Message] as const;\n\ntype MessageHandler = (event: MessageEvent) => void;\ntype ErrorHandler = (error: Error) => void;\n\nexport class Bot {\n readonly id: string;\n readonly name: string;\n\n private _status: BotStatus;\n private readonly client: Client;\n private readonly sentMessages: LimitedCollection<string, SentMessage>;\n private _connectedAt: Date | null = null;\n private readonly messageHandlers: MessageHandler[] = [];\n private readonly errorHandlers: ErrorHandler[] = [];\n private readonly _includeBotMessages: boolean;\n\n private constructor(\n id: string,\n name: string,\n client: Client,\n sentMessageCacheSize: number,\n includeBotMessages: boolean,\n ) {\n this.id = id;\n this.name = name;\n this.client = client;\n this.sentMessages = new LimitedCollection({ maxSize: sentMessageCacheSize });\n this._includeBotMessages = includeBotMessages;\n this._status = \"connecting\";\n }\n\n get includeBotMessages(): boolean {\n return this._includeBotMessages;\n }\n\n get status(): BotStatus {\n return this._status;\n }\n\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n get connectedAt(): Date | null {\n return this._connectedAt;\n }\n\n /**\n * Fast path: create a bot from a complete config, connect, verify, and\n * return a ready Bot — or throw with diagnostics.\n */\n static async fromConfig(config: BotConfig): Promise<Bot> {\n const cacheSize = config.sentMessageCacheSize ?? DEFAULT_SENT_MESSAGE_CACHE_SIZE;\n if (cacheSize < 1 || !Number.isInteger(cacheSize)) {\n throw new Error(\n `sentMessageCacheSize must be a positive integer, got ${String(cacheSize)}`,\n );\n }\n\n const includeBotMessages = config.includeBotMessages ?? false;\n\n const client = new Client({\n intents: config.intents ?? [...DEFAULT_INTENTS],\n partials: config.partials ?? [...DEFAULT_PARTIALS],\n });\n\n const bot = new Bot(config.id, config.name, client, cacheSize, includeBotMessages);\n\n await bot.connect(config.token);\n bot.verify();\n bot.setupEventHandlers();\n bot._status = \"ready\";\n bot._connectedAt = new Date();\n\n return bot;\n }\n\n // --- Sending ---\n\n async send(channelId: string, content: MessageContent): Promise<SentMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload) =>\n (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload),\n );\n }\n\n async sendDM(userId: string, content: MessageContent): Promise<SentMessage[]> {\n const user = await this.client.users.fetch(userId);\n return this.sendPayloads(content, (payload) =>\n user.send(payload as Parameters<typeof user.send>[0]),\n );\n }\n\n async reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]> {\n const { channel, message } = await this.fetchMessage(channelId, messageId, \"reply to\");\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload, i) => {\n if (i === 0) {\n return message.reply(payload as Parameters<Message[\"reply\"]>[0]);\n }\n return (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload);\n });\n }\n\n // --- Channel operations ---\n\n async react(channelId: string, messageId: string, emoji: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"react to\");\n try {\n await message.react(emoji);\n } catch (error) {\n throw new Error(\n `Cannot react to message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async editMessage(channelId: string, messageId: string, content: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"edit\");\n try {\n await message.edit(content);\n } catch (error) {\n throw new Error(\n `Cannot edit message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n const clamped = Math.max(1, Math.min(limit ?? 20, 100));\n const messages = await channel.messages.fetch({ limit: clamped });\n return [...messages.values()].reverse().map((m) => this.toFetchedMessage(m));\n }\n\n async *fetchHistory(\n channelId: string,\n options: FetchHistoryOptions,\n ): AsyncGenerator<FetchedMessage[]> {\n const { after, before, limit = 500 } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS consumers\n if (after && before) {\n throw new Error('Cannot specify both \"after\" and \"before\"');\n }\n\n let cursor: string;\n let direction: \"after\" | \"before\";\n if (after) {\n cursor = after;\n direction = \"after\";\n } else if (before) {\n cursor = before;\n direction = \"before\";\n } else {\n throw new Error('At least one of \"after\" or \"before\" must be specified');\n }\n\n const effectiveLimit = Math.min(limit, 5000);\n const channel = await this.fetchTextChannel(channelId);\n\n let totalFetched = 0;\n\n while (totalFetched < effectiveLimit) {\n const pageSize = Math.min(100, effectiveLimit - totalFetched);\n\n const fetched = await this.fetchPageWithRetry(channel, {\n limit: pageSize,\n [direction]: cursor,\n });\n\n if (fetched.size === 0) break;\n\n const rawMessages = [...fetched.values()];\n const messages = rawMessages.map((m) => this.toFetchedMessage(m));\n\n yield messages;\n totalFetched += messages.length;\n\n const lastMessage = rawMessages.at(-1);\n if (!lastMessage) break;\n cursor = lastMessage.id;\n\n if (fetched.size < pageSize) break;\n }\n }\n\n async getMessageAttachments(\n channelId: string,\n messageId: string,\n ): Promise<DownloadableAttachment[]> {\n const { message } = await this.fetchMessage(channelId, messageId, \"fetch attachments for\");\n return [...message.attachments.values()].map((att) => ({\n id: att.id,\n size: att.size,\n name: att.name,\n url: att.url,\n contentType: att.contentType,\n }));\n }\n\n // --- Receiving (event-driven) ---\n\n onMessage(handler: MessageHandler): () => void {\n this.messageHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.messageHandlers.indexOf(handler);\n if (index !== -1) this.messageHandlers.splice(index, 1);\n };\n }\n\n // --- Receiving (async iterable) ---\n\n messages(options?: { filter?: MessageFilter }): AsyncIterable<MessageEvent> {\n const cleanup: { unsub?: () => void } = {};\n const buffer = new EventBuffer<MessageEvent>({\n onClose: () => cleanup.unsub?.(),\n });\n cleanup.unsub = this.onMessage((event) => {\n if (options?.filter && !options.filter(event)) return;\n buffer.push(event);\n });\n return buffer;\n }\n\n // --- Error handling ---\n\n on(_event: \"error\", handler: ErrorHandler): () => void {\n this.errorHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.errorHandlers.indexOf(handler);\n if (index !== -1) this.errorHandlers.splice(index, 1);\n };\n }\n\n private emitError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n // --- Lifecycle ---\n\n async disconnect(): Promise<void> {\n this.client.removeAllListeners();\n await this.client.destroy();\n this.messageHandlers.length = 0;\n this.errorHandlers.length = 0;\n this._status = \"disconnected\";\n this._connectedAt = null;\n }\n\n // --- Private ---\n\n private async fetchTextChannel(channelId: string) {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) {\n throw new Error(\n `Channel ${channelId} is not a text-based channel or does not exist`,\n );\n }\n return channel;\n }\n\n private async fetchMessage(\n channelId: string,\n messageId: string,\n verb: string,\n ): Promise<{ channel: Awaited<ReturnType<Bot[\"fetchTextChannel\"]>>; message: Message }> {\n const channel = await this.fetchTextChannel(channelId);\n try {\n const message = await channel.messages.fetch(messageId);\n return { channel, message };\n } catch (error) {\n throw new Error(\n `Cannot ${verb} message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private toFetchedMessage(msg: Message): FetchedMessage {\n return {\n messageId: msg.id,\n channelId: msg.channelId,\n author: {\n id: msg.author.id,\n username: msg.author.username,\n bot: msg.author.bot,\n },\n content: msg.content,\n timestamp: msg.createdAt,\n attachmentCount: msg.attachments.size,\n };\n }\n\n private async fetchPageWithRetry(\n channel: { messages: { fetch(opts: unknown): Promise<Map<string, Message>> } },\n options: Record<string, unknown>,\n maxRetryMs = 5000,\n ): Promise<Map<string, Message>> {\n try {\n return await channel.messages.fetch(options);\n } catch (err) {\n if (err instanceof Error && \"retryAfter\" in err) {\n const { retryAfter } = err;\n if (typeof retryAfter === \"number\" && retryAfter <= maxRetryMs) {\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n return channel.messages.fetch(options);\n }\n }\n throw err;\n }\n }\n\n private async connect(token: string): Promise<void> {\n this._status = \"connecting\";\n await this.client.login(token);\n }\n\n private verify(): void {\n this._status = \"verifying\";\n\n if (this.client.guilds.cache.size === 0) {\n this._status = \"failed\";\n throw new Error(\n \"Bot is not in any guild. Invite the bot to at least one server before connecting.\",\n );\n }\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.MessageCreate, (message: Message) => {\n try {\n // Always ignore own messages (prevent infinite loops)\n if (message.author.id === this.client.user?.id) return;\n // Ignore other bots unless opted in\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n }\n\n private toMessageEvent(message: Message): MessageEvent {\n const isDM = message.channel.type === ChannelType.DM;\n const botUser = this.client.user;\n const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);\n\n let isReply = false;\n let replyTo: SentMessage | null = null;\n if (message.reference?.messageId) {\n const original = this.sentMessages.get(message.reference.messageId);\n if (original) {\n isReply = true;\n replyTo = original;\n }\n }\n\n return {\n messageId: message.id,\n author: {\n id: message.author.id,\n username: message.author.username,\n bot: message.author.bot,\n },\n content: message.content,\n channelId: message.channelId,\n guildId: message.guildId ?? null,\n timestamp: message.createdAt,\n mentions: [...message.mentions.users.keys()],\n raw: message,\n isDM,\n isMention,\n isReply,\n replyTo,\n };\n }\n\n private async sendPayloads(\n content: MessageContent,\n dispatcher: (payload: ChunkedPayload, index: number) => Promise<Message>,\n ): Promise<SentMessage[]> {\n const files =\n typeof content !== \"string\" && content.files?.length\n ? await validateAndBuildAttachments(content.files)\n : undefined;\n const payloads = toChunkedPayloads(content, files);\n const results: SentMessage[] = [];\n let i = 0;\n for (const payload of payloads) {\n const msg = await dispatcher(payload, i++);\n results.push(this.trackSentMessage(msg));\n }\n return results;\n }\n\n private trackSentMessage(message: Message): SentMessage {\n const sent: SentMessage = {\n messageId: message.id,\n channelId: message.channelId,\n timestamp: message.createdAt,\n raw: message,\n };\n this.sentMessages.set(sent.messageId, sent);\n return sent;\n }\n}\n\ninterface ChunkedPayload {\n content?: string;\n embeds?: unknown[];\n files?: AttachmentBuilder[];\n}\n\nfunction toChunkedPayloads(\n content: MessageContent,\n files?: AttachmentBuilder[],\n): ChunkedPayload[] {\n if (typeof content === \"string\") {\n const chunks = chunkMessage(content);\n return chunks.map((text, i) => ({\n content: text,\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n }\n const text = content.content ?? \"\";\n const chunks = chunkMessage(text);\n if (chunks.length === 0) {\n return [{ embeds: content.embeds, ...(files?.length ? { files } : {}) }];\n }\n return chunks.map((chunk, i) => ({\n content: chunk,\n ...(i === 0 && content.embeds ? { embeds: content.embeds } : {}),\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n}\n\nconst MAX_FILES_PER_MESSAGE = 10;\n\nasync function validateAndBuildAttachments(\n files: FileAttachment[],\n): Promise<AttachmentBuilder[]> {\n if (files.length > MAX_FILES_PER_MESSAGE) {\n throw new Error(\n `Too many attachments: ${String(files.length)} exceeds Discord's limit of ${String(MAX_FILES_PER_MESSAGE)} attachments per message`,\n );\n }\n\n const sizes = await Promise.all(\n files.map((file) =>\n typeof file.data === \"string\"\n ? stat(file.data).then((st) => st.size)\n : Promise.resolve(file.data.byteLength),\n ),\n );\n\n return files.map((file, i) => {\n const size = sizes[i];\n if (size === undefined) throw new Error(`Missing size for attachment: ${file.name}`);\n validateAttachmentSize({ size, name: file.name, id: file.name });\n return new AttachmentBuilder(file.data, { name: file.name });\n });\n}\n","/**\n * Single-consumer async iterable buffer. Each call to messages() on a Bot\n * creates its own buffer. Do not share a single EventBuffer between\n * multiple for-await consumers.\n */\nexport class EventBuffer<T> implements AsyncIterable<T> {\n private buffer: T[] = [];\n private resolve: ((value: IteratorResult<T>) => void) | null = null;\n private closed = false;\n private readonly onClose: (() => void) | undefined;\n\n constructor(options?: { onClose?: () => void }) {\n this.onClose = options?.onClose;\n }\n\n push(value: T): void {\n if (this.closed) return;\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value, done: false });\n } else {\n this.buffer.push(value);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.onClose?.();\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value: undefined as unknown as T, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.buffer.length > 0) {\n // Length check guarantees shift() returns a value\n const value = this.buffer.shift() as T;\n return Promise.resolve({ value, done: false });\n }\n if (this.closed) {\n return Promise.resolve({\n value: undefined as unknown as T,\n done: true,\n });\n }\n return new Promise((resolve) => {\n this.resolve = resolve;\n });\n },\n return: (): Promise<IteratorResult<T>> => {\n this.close();\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n },\n };\n }\n}\n","// Message utility functions for discord-bot-lib\n\n/** Discord's hard limit on message length. */\nexport const DISCORD_MAX_MESSAGE_LENGTH = 2000;\n\n/**\n * Splits text into chunks that fit within Discord's message length limit.\n * Prefers splitting at paragraph boundaries (\\n\\n), then line breaks (\\n),\n * then spaces, then hard-cuts as a last resort.\n *\n * @param text - The text to split\n * @param limit - Maximum characters per chunk (default: {@link DISCORD_MAX_MESSAGE_LENGTH})\n * @returns Array of text chunks, each within the limit\n */\nexport function chunkMessage(\n text: string,\n limit: number = DISCORD_MAX_MESSAGE_LENGTH,\n): string[] {\n if (!text) return [];\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const halfLimit = limit / 2;\n let rest = text;\n\n while (rest.length > limit) {\n const para = rest.lastIndexOf(\"\\n\\n\", limit);\n const line = rest.lastIndexOf(\"\\n\", limit);\n const space = rest.lastIndexOf(\" \", limit);\n const cut =\n para > halfLimit\n ? para\n : line > halfLimit\n ? line\n : space > 0\n ? space\n : limit;\n\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).replace(/^\\n+/, \"\");\n }\n\n if (rest) chunks.push(rest);\n\n return chunks;\n}\n","// Attachment utility functions for discord-bot-lib\n\n/** Maximum attachment size in bytes (25 MB). Matches Discord's default file size limit. */\nexport const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024; // 26_214_400\n\n/** Shape compatible with discord.js Attachment for attachment utilities. */\nexport interface AttachmentLike {\n size: number;\n name?: string | null;\n id: string;\n}\n\n/**\n * Validates that an attachment does not exceed Discord's size limit.\n * Designed for use in both upload (sending files) and download paths.\n *\n * @throws Error if the attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n */\nexport function validateAttachmentSize(attachment: AttachmentLike): void {\n if (attachment.size > MAX_ATTACHMENT_BYTES) {\n const actualMB = (attachment.size / 1024 / 1024).toFixed(1);\n const limitMB = MAX_ATTACHMENT_BYTES / 1024 / 1024;\n const nameInfo = attachment.name ? `: ${attachment.name}` : \"\";\n throw new Error(\n `Attachment too large (${actualMB} MB > ${String(limitMB)} MB limit)${nameInfo}`,\n );\n }\n}\n\n/**\n * Sanitizes an attachment filename by replacing unsafe characters\n * (`[`, `]`, `\\r`, `\\n`, `;`) with underscores. Falls back to the\n * attachment ID when the name is missing or empty after sanitization.\n */\nconst UNSAFE_CHARS = /[[\\]\\r\\n;]/g;\n\nexport function sanitizeAttachmentName(\n attachment: Pick<AttachmentLike, \"name\" | \"id\">,\n): string {\n const raw = attachment.name;\n if (!raw || raw.replace(UNSAFE_CHARS, \"\").length === 0) {\n return attachment.id;\n }\n return raw.replace(UNSAFE_CHARS, \"_\");\n}\n\n/** Result of downloading a Discord attachment. */\nexport interface DownloadedAttachment {\n buffer: Buffer;\n filename: string;\n contentType: string;\n}\n\n/** Shape accepted by {@link downloadAttachment}. Compatible with discord.js `Attachment`. */\nexport interface DownloadableAttachment extends AttachmentLike {\n url: string;\n contentType?: string | null;\n}\n\n/**\n * Downloads a Discord message attachment to a raw buffer.\n * Validates size and sanitizes filename before returning.\n *\n * @throws Error if attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n * @throws Error if the fetch response is not ok\n */\nexport async function downloadAttachment(\n attachment: DownloadableAttachment,\n): Promise<DownloadedAttachment> {\n validateAttachmentSize(attachment);\n\n const res = await fetch(attachment.url);\n if (!res.ok) {\n throw new Error(\n `Failed to download attachment: ${String(res.status)} ${res.statusText}`,\n );\n }\n\n const ab = await res.arrayBuffer();\n const buffer = Buffer.from(ab, 0, ab.byteLength);\n const filename = sanitizeAttachmentName(attachment);\n const contentType = attachment.contentType ?? \"application/octet-stream\";\n\n return { buffer, filename, contentType };\n}\n","import { Client } from \"discord.js\";\nimport type { OnboardingStep, OnboardingStepStatus, StepResult } from \"./types.js\";\nimport { Bot, DEFAULT_INTENTS, DEFAULT_PARTIALS } from \"./bot.js\";\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\ntype StepAction = (...args: unknown[]) => Promise<StepResult>;\n\nclass OnboardingStepImpl implements OnboardingStep {\n id: string;\n label: string;\n instructions: string;\n status: OnboardingStepStatus;\n error?: string;\n nextSibling?: OnboardingStepImpl;\n\n private readonly _action: StepAction;\n private readonly _dependencies: OnboardingStepImpl[];\n\n constructor(\n id: string,\n label: string,\n instructions: string,\n action: StepAction,\n dependencies: OnboardingStepImpl[] = [],\n initialStatus: OnboardingStepStatus = \"pending\",\n ) {\n this.id = id;\n this.label = label;\n this.instructions = instructions;\n this._action = action;\n this._dependencies = dependencies;\n this.status = initialStatus;\n }\n\n async complete(...args: unknown[]): Promise<StepResult> {\n // Check dependencies\n for (const dep of this._dependencies) {\n if (dep.status !== \"completed\") {\n const error = `Cannot complete '${this.id}': dependency '${dep.id}' is not completed.`;\n this.status = \"failed\";\n this.error = error;\n return { success: false, error };\n }\n }\n\n try {\n const result = await this._action(...args);\n if (result.success) {\n this.status = \"completed\";\n this.error = undefined;\n // Promote next step to ready\n if (this.nextSibling?.status === \"pending\") {\n this.nextSibling.status = \"ready\";\n result.nextStep = this.nextSibling;\n }\n } else {\n this.status = \"failed\";\n this.error = result.error;\n }\n return result;\n } catch (err) {\n const message = toErrorMessage(err);\n this.status = \"failed\";\n this.error = message;\n return { success: false, error: message };\n }\n }\n}\n\nexport class BotOnboarding {\n readonly id: string;\n readonly name: string;\n readonly steps: OnboardingStep[];\n\n private _bot: Bot | undefined;\n private _token: string | undefined;\n\n constructor(id: string, name: string) {\n this.id = id;\n this.name = name;\n const provideToken = new OnboardingStepImpl(\n \"provide_token\",\n \"Provide Bot Token\",\n \"Enter your Discord bot token. You can find this in the Discord Developer Portal under your application's Bot settings.\",\n async (...args: unknown[]) => {\n const token = args[0] as string;\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(token);\n await client.destroy();\n this._token = token;\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [],\n \"ready\",\n );\n\n const inviteToServer = new OnboardingStepImpl(\n \"invite_to_server\",\n \"Invite Bot to Server\",\n \"Add the bot to at least one Discord server using the OAuth2 URL from the Developer Portal.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(this._token);\n if (client.guilds.cache.size === 0) {\n await client.destroy();\n return { success: false, error: \"Bot is not in any server yet\" };\n }\n await client.destroy();\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [provideToken],\n );\n\n const verifyPermissions = new OnboardingStepImpl(\n \"verify_permissions\",\n \"Verify Permissions\",\n \"Verify the bot has the required permissions in your server.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n try {\n this._bot = await Bot.fromConfig({\n id: this.id,\n name: this.name,\n token: this._token,\n });\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [inviteToServer],\n );\n\n // Wire nextSibling chain for step promotion\n provideToken.nextSibling = inviteToServer;\n inviteToServer.nextSibling = verifyPermissions;\n\n this.steps = [provideToken, inviteToServer, verifyPermissions];\n }\n\n get bot(): Bot | undefined {\n return this._bot;\n }\n}\n","import { Bot } from \"./bot.js\";\nimport { BotOnboarding } from \"./onboarding.js\";\nimport type { MessageEvent } from \"./types.js\";\n\ntype LifecycleEvent = \"botReady\" | \"botDisconnected\" | \"botError\";\n\nexport class ConnectorManager {\n private botMap = new Map<string, Bot>();\n private botUnsubscribers = new Map<string, (() => void)[]>();\n private messageHandlers: ((event: MessageEvent, bot: Bot) => void)[] = [];\n private lifecycleHandlers = new Map<LifecycleEvent, ((...args: unknown[]) => void)[]>();\n\n addBot(bot: Bot): void {\n if (this.botMap.has(bot.id)) {\n throw new Error(`Bot with id \"${bot.id}\" already exists in this manager.`);\n }\n this.botMap.set(bot.id, bot);\n\n const unsubs: (() => void)[] = [];\n unsubs.push(\n bot.onMessage((event) => {\n for (const handler of this.messageHandlers) {\n handler(event, bot);\n }\n }),\n );\n unsubs.push(\n bot.on(\"error\", (error) => {\n this.emitLifecycle(\"botError\", bot, error);\n }),\n );\n this.botUnsubscribers.set(bot.id, unsubs);\n }\n\n getBot(id: string): Bot | undefined {\n return this.botMap.get(id);\n }\n\n get bots(): Iterable<Bot> {\n return this.botMap.values();\n }\n\n removeBot(id: string): void {\n const unsubs = this.botUnsubscribers.get(id);\n if (unsubs) {\n for (const unsub of unsubs) unsub();\n this.botUnsubscribers.delete(id);\n }\n this.botMap.delete(id);\n }\n\n status(): { id: string; name: string; status: string }[] {\n return [...this.botMap.values()].map((bot) => ({\n id: bot.id,\n name: bot.name,\n status: bot.status,\n }));\n }\n\n onMessage(handler: (event: MessageEvent, bot: Bot) => void): void {\n this.messageHandlers.push(handler);\n }\n\n on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void {\n const handlers = this.lifecycleHandlers.get(event) ?? [];\n handlers.push(callback);\n this.lifecycleHandlers.set(event, handlers);\n }\n\n private emitLifecycle(event: LifecycleEvent, ...args: unknown[]): void {\n const handlers = this.lifecycleHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n handler(...args);\n }\n }\n }\n\n onboardBot(id: string, name: string): BotOnboarding {\n return new BotOnboarding(id, name);\n }\n\n async disconnectAll(): Promise<void> {\n await Promise.all([...this.botMap.values()].map((bot) => bot.disconnect()));\n }\n\n async shutdown(): Promise<void> {\n await this.disconnectAll();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAA6H;AAC7H,sBAAqB;;;ACId,IAAM,cAAN,MAAiD;AAAA,EAC9C,SAAc,CAAC;AAAA,EACf,UAAuD;AAAA,EACvD,SAAS;AAAA,EACA;AAAA,EAEjB,YAAY,SAAoC;AAC9C,SAAK,UAAU,SAAS;AAAA,EAC1B;AAAA,EAEA,KAAK,OAAgB;AACnB,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,KAAK;AACf,WAAK,UAAU;AACf,QAAE,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,IAC1B,OAAO;AACL,WAAK,OAAO,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,UAAU;AACf,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,KAAK;AACf,WAAK,UAAU;AACf,QAAE,EAAE,OAAO,QAA2B,MAAM,KAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,CAAC,OAAO,aAAa,IAAsB;AACzC,WAAO;AAAA,MACL,MAAM,MAAkC;AACtC,YAAI,KAAK,OAAO,SAAS,GAAG;AAE1B,gBAAM,QAAQ,KAAK,OAAO,MAAM;AAChC,iBAAO,QAAQ,QAAQ,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,QAC/C;AACA,YAAI,KAAK,QAAQ;AACf,iBAAO,QAAQ,QAAQ;AAAA,YACrB,OAAO;AAAA,YACP,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AACA,eAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,eAAK,UAAU;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,MACA,QAAQ,MAAkC;AACxC,aAAK,MAAM;AACX,eAAO,QAAQ,QAAQ,EAAE,OAAO,QAA2B,MAAM,KAAK,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;;;AC1DO,IAAM,6BAA6B;AAWnC,SAAS,aACd,MACA,QAAgB,4BACN;AACV,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,MAAI,KAAK,UAAU,MAAO,QAAO,CAAC,IAAI;AAEtC,QAAM,SAAmB,CAAC;AAC1B,QAAM,YAAY,QAAQ;AAC1B,MAAI,OAAO;AAEX,SAAO,KAAK,SAAS,OAAO;AAC1B,UAAM,OAAO,KAAK,YAAY,QAAQ,KAAK;AAC3C,UAAM,OAAO,KAAK,YAAY,MAAM,KAAK;AACzC,UAAM,QAAQ,KAAK,YAAY,KAAK,KAAK;AACzC,UAAM,MACJ,OAAO,YACH,OACA,OAAO,YACL,OACA,QAAQ,IACN,QACA;AAEV,WAAO,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAC9B,WAAO,KAAK,MAAM,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAAA,EAC3C;AAEA,MAAI,KAAM,QAAO,KAAK,IAAI;AAE1B,SAAO;AACT;;;AC1CO,IAAM,uBAAuB,KAAK,OAAO;AAezC,SAAS,uBAAuB,YAAkC;AACvE,MAAI,WAAW,OAAO,sBAAsB;AAC1C,UAAM,YAAY,WAAW,OAAO,OAAO,MAAM,QAAQ,CAAC;AAC1D,UAAM,UAAU,uBAAuB,OAAO;AAC9C,UAAM,WAAW,WAAW,OAAO,KAAK,WAAW,IAAI,KAAK;AAC5D,UAAM,IAAI;AAAA,MACR,yBAAyB,QAAQ,SAAS,OAAO,OAAO,CAAC,aAAa,QAAQ;AAAA,IAChF;AAAA,EACF;AACF;AAOA,IAAM,eAAe;AAEd,SAAS,uBACd,YACQ;AACR,QAAM,MAAM,WAAW;AACvB,MAAI,CAAC,OAAO,IAAI,QAAQ,cAAc,EAAE,EAAE,WAAW,GAAG;AACtD,WAAO,WAAW;AAAA,EACpB;AACA,SAAO,IAAI,QAAQ,cAAc,GAAG;AACtC;AAsBA,eAAsB,mBACpB,YAC+B;AAC/B,yBAAuB,UAAU;AAEjC,QAAM,MAAM,MAAM,MAAM,WAAW,GAAG;AACtC,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI;AAAA,MACR,kCAAkC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,UAAU;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,KAAK,MAAM,IAAI,YAAY;AACjC,QAAM,SAAS,OAAO,KAAK,IAAI,GAAG,GAAG,UAAU;AAC/C,QAAM,WAAW,uBAAuB,UAAU;AAClD,QAAM,cAAc,WAAW,eAAe;AAE9C,SAAO,EAAE,QAAQ,UAAU,YAAY;AACzC;;;AHnEO,IAAM,kCAAkC;AAExC,IAAM,kBAAkB;AAAA,EAC7B,iCAAkB;AAAA,EAClB,iCAAkB;AAAA,EAClB,iCAAkB;AAAA,EAClB,iCAAkB;AACpB;AAEO,IAAM,mBAAmB,CAAC,wBAAS,SAAS,wBAAS,OAAO;AAK5D,IAAM,MAAN,MAAM,KAAI;AAAA,EACN;AAAA,EACA;AAAA,EAED;AAAA,EACS;AAAA,EACA;AAAA,EACT,eAA4B;AAAA,EACnB,kBAAoC,CAAC;AAAA,EACrC,gBAAgC,CAAC;AAAA,EACjC;AAAA,EAET,YACN,IACA,MACA,QACA,sBACA,oBACA;AACA,SAAK,KAAK;AACV,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,eAAe,IAAI,iCAAkB,EAAE,SAAS,qBAAqB,CAAC;AAC3E,SAAK,sBAAsB;AAC3B,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,IAAI,qBAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO,OAAO,MAAM;AAAA,EAClC;AAAA,EAEA,IAAI,cAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAAW,QAAiC;AACvD,UAAM,YAAY,OAAO,wBAAwB;AACjD,QAAI,YAAY,KAAK,CAAC,OAAO,UAAU,SAAS,GAAG;AACjD,YAAM,IAAI;AAAA,QACR,wDAAwD,OAAO,SAAS,CAAC;AAAA,MAC3E;AAAA,IACF;AAEA,UAAM,qBAAqB,OAAO,sBAAsB;AAExD,UAAM,SAAS,IAAI,sBAAO;AAAA,MACxB,SAAS,OAAO,WAAW,CAAC,GAAG,eAAe;AAAA,MAC9C,UAAU,OAAO,YAAY,CAAC,GAAG,gBAAgB;AAAA,IACnD,CAAC;AAED,UAAM,MAAM,IAAI,KAAI,OAAO,IAAI,OAAO,MAAM,QAAQ,WAAW,kBAAkB;AAEjF,UAAM,IAAI,QAAQ,OAAO,KAAK;AAC9B,QAAI,OAAO;AACX,QAAI,mBAAmB;AACvB,QAAI,UAAU;AACd,QAAI,eAAe,oBAAI,KAAK;AAE5B,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAM,KAAK,WAAmB,SAAiD;AAC7E,UAAM,UAAU,MAAM,KAAK,iBAAiB,SAAS;AACrD,QAAI,EAAE,UAAU,UAAU;AACxB,YAAM,IAAI,MAAM,WAAW,SAAS,4BAA4B;AAAA,IAClE;AACA,WAAO,KAAK;AAAA,MAAa;AAAA,MAAS,CAAC,YAChC,QAAiE,KAAK,OAAO;AAAA,IAChF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,QAAgB,SAAiD;AAC5E,UAAM,OAAO,MAAM,KAAK,OAAO,MAAM,MAAM,MAAM;AACjD,WAAO,KAAK;AAAA,MAAa;AAAA,MAAS,CAAC,YACjC,KAAK,KAAK,OAA0C;AAAA,IACtD;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,WAAmB,WAAmB,SAAiD;AACjG,UAAM,EAAE,SAAS,QAAQ,IAAI,MAAM,KAAK,aAAa,WAAW,WAAW,UAAU;AACrF,QAAI,EAAE,UAAU,UAAU;AACxB,YAAM,IAAI,MAAM,WAAW,SAAS,4BAA4B;AAAA,IAClE;AACA,WAAO,KAAK,aAAa,SAAS,CAAC,SAAS,MAAM;AAChD,UAAI,MAAM,GAAG;AACX,eAAO,QAAQ,MAAM,OAA0C;AAAA,MACjE;AACA,aAAQ,QAAiE,KAAK,OAAO;AAAA,IACvF,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,MAAM,WAAmB,WAAmB,OAA8B;AAC9E,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,aAAa,WAAW,WAAW,UAAU;AAC5E,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK;AAAA,IAC3B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,2BAA2B,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACjG;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,WAAmB,WAAmB,SAAgC;AACtF,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,aAAa,WAAW,WAAW,MAAM;AACxE,QAAI;AACF,YAAM,QAAQ,KAAK,OAAO;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,uBAAuB,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,WAAmB,OAA2C;AAChF,UAAM,UAAU,MAAM,KAAK,iBAAiB,SAAS;AACrD,UAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,IAAI,GAAG,CAAC;AACtD,UAAM,WAAW,MAAM,QAAQ,SAAS,MAAM,EAAE,OAAO,QAAQ,CAAC;AAChE,WAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,KAAK,iBAAiB,CAAC,CAAC;AAAA,EAC7E;AAAA,EAEA,OAAO,aACL,WACA,SACkC;AAClC,UAAM,EAAE,OAAO,QAAQ,QAAQ,IAAI,IAAI;AAGvC,QAAI,SAAS,QAAQ;AACnB,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI,OAAO;AACT,eAAS;AACT,kBAAY;AAAA,IACd,WAAW,QAAQ;AACjB,eAAS;AACT,kBAAY;AAAA,IACd,OAAO;AACL,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAEA,UAAM,iBAAiB,KAAK,IAAI,OAAO,GAAI;AAC3C,UAAM,UAAU,MAAM,KAAK,iBAAiB,SAAS;AAErD,QAAI,eAAe;AAEnB,WAAO,eAAe,gBAAgB;AACpC,YAAM,WAAW,KAAK,IAAI,KAAK,iBAAiB,YAAY;AAE5D,YAAM,UAAU,MAAM,KAAK,mBAAmB,SAAS;AAAA,QACrD,OAAO;AAAA,QACP,CAAC,SAAS,GAAG;AAAA,MACf,CAAC;AAED,UAAI,QAAQ,SAAS,EAAG;AAExB,YAAM,cAAc,CAAC,GAAG,QAAQ,OAAO,CAAC;AACxC,YAAM,WAAW,YAAY,IAAI,CAAC,MAAM,KAAK,iBAAiB,CAAC,CAAC;AAEhE,YAAM;AACN,sBAAgB,SAAS;AAEzB,YAAM,cAAc,YAAY,GAAG,EAAE;AACrC,UAAI,CAAC,YAAa;AAClB,eAAS,YAAY;AAErB,UAAI,QAAQ,OAAO,SAAU;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAM,sBACJ,WACA,WACmC;AACnC,UAAM,EAAE,QAAQ,IAAI,MAAM,KAAK,aAAa,WAAW,WAAW,uBAAuB;AACzF,WAAO,CAAC,GAAG,QAAQ,YAAY,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AAAA,MACrD,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT,aAAa,IAAI;AAAA,IACnB,EAAE;AAAA,EACJ;AAAA;AAAA,EAIA,UAAU,SAAqC;AAC7C,SAAK,gBAAgB,KAAK,OAAO;AACjC,QAAI,UAAU;AACd,WAAO,MAAM;AACX,UAAI,QAAS;AACb,gBAAU;AACV,YAAM,QAAQ,KAAK,gBAAgB,QAAQ,OAAO;AAClD,UAAI,UAAU,GAAI,MAAK,gBAAgB,OAAO,OAAO,CAAC;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAIA,SAAS,SAAmE;AAC1E,UAAM,UAAkC,CAAC;AACzC,UAAM,SAAS,IAAI,YAA0B;AAAA,MAC3C,SAAS,MAAM,QAAQ,QAAQ;AAAA,IACjC,CAAC;AACD,YAAQ,QAAQ,KAAK,UAAU,CAAC,UAAU;AACxC,UAAI,SAAS,UAAU,CAAC,QAAQ,OAAO,KAAK,EAAG;AAC/C,aAAO,KAAK,KAAK;AAAA,IACnB,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,GAAG,QAAiB,SAAmC;AACrD,SAAK,cAAc,KAAK,OAAO;AAC/B,QAAI,UAAU;AACd,WAAO,MAAM;AACX,UAAI,QAAS;AACb,gBAAU;AACV,YAAM,QAAQ,KAAK,cAAc,QAAQ,OAAO;AAChD,UAAI,UAAU,GAAI,MAAK,cAAc,OAAO,OAAO,CAAC;AAAA,IACtD;AAAA,EACF;AAAA,EAEQ,UAAU,OAAsB;AACtC,UAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,eAAW,WAAW,KAAK,eAAe;AACxC,cAAQ,GAAG;AAAA,IACb;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,aAA4B;AAChC,SAAK,OAAO,mBAAmB;AAC/B,UAAM,KAAK,OAAO,QAAQ;AAC1B,SAAK,gBAAgB,SAAS;AAC9B,SAAK,cAAc,SAAS;AAC5B,SAAK,UAAU;AACf,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAIA,MAAc,iBAAiB,WAAmB;AAChD,UAAM,UAAU,MAAM,KAAK,OAAO,SAAS,MAAM,SAAS;AAC1D,QAAI,CAAC,SAAS,YAAY,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,WAAW,SAAS;AAAA,MACtB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,aACZ,WACA,WACA,MACsF;AACtF,UAAM,UAAU,MAAM,KAAK,iBAAiB,SAAS;AACrD,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,SAAS,MAAM,SAAS;AACtD,aAAO,EAAE,SAAS,QAAQ;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,UAAU,IAAI,YAAY,SAAS,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAA8B;AACrD,WAAO;AAAA,MACL,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,QAAQ;AAAA,QACN,IAAI,IAAI,OAAO;AAAA,QACf,UAAU,IAAI,OAAO;AAAA,QACrB,KAAK,IAAI,OAAO;AAAA,MAClB;AAAA,MACA,SAAS,IAAI;AAAA,MACb,WAAW,IAAI;AAAA,MACf,iBAAiB,IAAI,YAAY;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,SACA,SACA,aAAa,KACkB;AAC/B,QAAI;AACF,aAAO,MAAM,QAAQ,SAAS,MAAM,OAAO;AAAA,IAC7C,SAAS,KAAK;AACZ,UAAI,eAAe,SAAS,gBAAgB,KAAK;AAC/C,cAAM,EAAE,WAAW,IAAI;AACvB,YAAI,OAAO,eAAe,YAAY,cAAc,YAAY;AAC9D,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,CAAC;AAC9D,iBAAO,QAAQ,SAAS,MAAM,OAAO;AAAA,QACvC;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,OAA8B;AAClD,SAAK,UAAU;AACf,UAAM,KAAK,OAAO,MAAM,KAAK;AAAA,EAC/B;AAAA,EAEQ,SAAe;AACrB,SAAK,UAAU;AAEf,QAAI,KAAK,OAAO,OAAO,MAAM,SAAS,GAAG;AACvC,WAAK,UAAU;AACf,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,qBAA2B;AACjC,SAAK,OAAO,GAAG,sBAAO,eAAe,CAAC,YAAqB;AACzD,UAAI;AAEF,YAAI,QAAQ,OAAO,OAAO,KAAK,OAAO,MAAM,GAAI;AAEhD,YAAI,QAAQ,OAAO,OAAO,CAAC,KAAK,oBAAqB;AAErD,cAAM,QAAQ,KAAK,eAAe,OAAO;AAEzC,mBAAW,WAAW,KAAK,iBAAiB;AAC1C,kBAAQ,KAAK;AAAA,QACf;AAAA,MACF,SAAS,OAAO;AACd,aAAK,UAAU,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,eAAe,SAAgC;AACrD,UAAM,OAAO,QAAQ,QAAQ,SAAS,2BAAY;AAClD,UAAM,UAAU,KAAK,OAAO;AAC5B,UAAM,YAAY,SAAS,UAAU,QAAQ,SAAS,IAAI,QAAQ,EAAE,IAAI;AAExE,QAAI,UAAU;AACd,QAAI,UAA8B;AAClC,QAAI,QAAQ,WAAW,WAAW;AAChC,YAAM,WAAW,KAAK,aAAa,IAAI,QAAQ,UAAU,SAAS;AAClE,UAAI,UAAU;AACZ,kBAAU;AACV,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,QAAQ;AAAA,QACN,IAAI,QAAQ,OAAO;AAAA,QACnB,UAAU,QAAQ,OAAO;AAAA,QACzB,KAAK,QAAQ,OAAO;AAAA,MACtB;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ,WAAW;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,UAAU,CAAC,GAAG,QAAQ,SAAS,MAAM,KAAK,CAAC;AAAA,MAC3C,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,aACZ,SACA,YACwB;AACxB,UAAM,QACJ,OAAO,YAAY,YAAY,QAAQ,OAAO,SAC1C,MAAM,4BAA4B,QAAQ,KAAK,IAC/C;AACN,UAAM,WAAW,kBAAkB,SAAS,KAAK;AACjD,UAAM,UAAyB,CAAC;AAChC,QAAI,IAAI;AACR,eAAW,WAAW,UAAU;AAC9B,YAAM,MAAM,MAAM,WAAW,SAAS,GAAG;AACzC,cAAQ,KAAK,KAAK,iBAAiB,GAAG,CAAC;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,SAA+B;AACtD,UAAM,OAAoB;AAAA,MACxB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,KAAK;AAAA,IACP;AACA,SAAK,aAAa,IAAI,KAAK,WAAW,IAAI;AAC1C,WAAO;AAAA,EACT;AACF;AAQA,SAAS,kBACP,SACA,OACkB;AAClB,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAMA,UAAS,aAAa,OAAO;AACnC,WAAOA,QAAO,IAAI,CAACC,OAAM,OAAO;AAAA,MAC9B,SAASA;AAAA,MACT,GAAI,MAAM,KAAK,OAAO,SAAS,EAAE,MAAM,IAAI,CAAC;AAAA,IAC9C,EAAE;AAAA,EACJ;AACA,QAAM,OAAO,QAAQ,WAAW;AAChC,QAAM,SAAS,aAAa,IAAI;AAChC,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC,EAAE,QAAQ,QAAQ,QAAQ,GAAI,OAAO,SAAS,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EACzE;AACA,SAAO,OAAO,IAAI,CAAC,OAAO,OAAO;AAAA,IAC/B,SAAS;AAAA,IACT,GAAI,MAAM,KAAK,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IAC9D,GAAI,MAAM,KAAK,OAAO,SAAS,EAAE,MAAM,IAAI,CAAC;AAAA,EAC9C,EAAE;AACJ;AAEA,IAAM,wBAAwB;AAE9B,eAAe,4BACb,OAC8B;AAC9B,MAAI,MAAM,SAAS,uBAAuB;AACxC,UAAM,IAAI;AAAA,MACR,yBAAyB,OAAO,MAAM,MAAM,CAAC,+BAA+B,OAAO,qBAAqB,CAAC;AAAA,IAC3G;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,MAAM;AAAA,MAAI,CAAC,SACT,OAAO,KAAK,SAAS,eACjB,sBAAK,KAAK,IAAI,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,IACpC,QAAQ,QAAQ,KAAK,KAAK,UAAU;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO,MAAM,IAAI,CAAC,MAAM,MAAM;AAC5B,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,OAAW,OAAM,IAAI,MAAM,gCAAgC,KAAK,IAAI,EAAE;AACnF,2BAAuB,EAAE,MAAM,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;AAC/D,WAAO,IAAI,iCAAkB,KAAK,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC;AAAA,EAC7D,CAAC;AACH;;;AI3fA,IAAAC,kBAAuB;AAIvB,SAAS,eAAe,KAAsB;AAC5C,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAIA,IAAM,qBAAN,MAAmD;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEiB;AAAA,EACA;AAAA,EAEjB,YACE,IACA,OACA,cACA,QACA,eAAqC,CAAC,GACtC,gBAAsC,WACtC;AACA,SAAK,KAAK;AACV,SAAK,QAAQ;AACb,SAAK,eAAe;AACpB,SAAK,UAAU;AACf,SAAK,gBAAgB;AACrB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,MAAsC;AAEtD,eAAW,OAAO,KAAK,eAAe;AACpC,UAAI,IAAI,WAAW,aAAa;AAC9B,cAAM,QAAQ,oBAAoB,KAAK,EAAE,kBAAkB,IAAI,EAAE;AACjE,aAAK,SAAS;AACd,aAAK,QAAQ;AACb,eAAO,EAAE,SAAS,OAAO,MAAM;AAAA,MACjC;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,QAAQ,GAAG,IAAI;AACzC,UAAI,OAAO,SAAS;AAClB,aAAK,SAAS;AACd,aAAK,QAAQ;AAEb,YAAI,KAAK,aAAa,WAAW,WAAW;AAC1C,eAAK,YAAY,SAAS;AAC1B,iBAAO,WAAW,KAAK;AAAA,QACzB;AAAA,MACF,OAAO;AACL,aAAK,SAAS;AACd,aAAK,QAAQ,OAAO;AAAA,MACtB;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,GAAG;AAClC,WAAK,SAAS;AACd,WAAK,QAAQ;AACb,aAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,IAC1C;AAAA,EACF;AACF;AAEO,IAAM,gBAAN,MAAoB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EAED;AAAA,EACA;AAAA,EAER,YAAY,IAAY,MAAc;AACpC,SAAK,KAAK;AACV,SAAK,OAAO;AACZ,UAAM,eAAe,IAAI;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,SAAoB;AAC5B,cAAM,QAAQ,KAAK,CAAC;AACpB,cAAM,SAAS,IAAI,uBAAO,EAAE,SAAS,CAAC,GAAG,eAAe,GAAG,UAAU,CAAC,GAAG,gBAAgB,EAAE,CAAC;AAC5F,YAAI;AACF,gBAAM,OAAO,MAAM,KAAK;AACxB,gBAAM,OAAO,QAAQ;AACrB,eAAK,SAAS;AACd,iBAAO,EAAE,SAAS,KAAK;AAAA,QACzB,SAAS,KAAK;AACZ,iBAAO,EAAE,SAAS,OAAO,OAAO,eAAe,GAAG,EAAE;AAAA,QACtD;AAAA,MACF;AAAA,MACA,CAAC;AAAA,MACD;AAAA,IACF;AAEA,UAAM,iBAAiB,IAAI;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY;AACV,YAAI,CAAC,KAAK,QAAQ;AAChB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,UACT;AAAA,QACF;AACA,cAAM,SAAS,IAAI,uBAAO,EAAE,SAAS,CAAC,GAAG,eAAe,GAAG,UAAU,CAAC,GAAG,gBAAgB,EAAE,CAAC;AAC5F,YAAI;AACF,gBAAM,OAAO,MAAM,KAAK,MAAM;AAC9B,cAAI,OAAO,OAAO,MAAM,SAAS,GAAG;AAClC,kBAAM,OAAO,QAAQ;AACrB,mBAAO,EAAE,SAAS,OAAO,OAAO,+BAA+B;AAAA,UACjE;AACA,gBAAM,OAAO,QAAQ;AACrB,iBAAO,EAAE,SAAS,KAAK;AAAA,QACzB,SAAS,KAAK;AACZ,iBAAO,EAAE,SAAS,OAAO,OAAO,eAAe,GAAG,EAAE;AAAA,QACtD;AAAA,MACF;AAAA,MACA,CAAC,YAAY;AAAA,IACf;AAEA,UAAM,oBAAoB,IAAI;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY;AACV,YAAI,CAAC,KAAK,QAAQ;AAChB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,UACT;AAAA,QACF;AACA,YAAI;AACF,eAAK,OAAO,MAAM,IAAI,WAAW;AAAA,YAC/B,IAAI,KAAK;AAAA,YACT,MAAM,KAAK;AAAA,YACX,OAAO,KAAK;AAAA,UACd,CAAC;AACD,iBAAO,EAAE,SAAS,KAAK;AAAA,QACzB,SAAS,KAAK;AACZ,iBAAO,EAAE,SAAS,OAAO,OAAO,eAAe,GAAG,EAAE;AAAA,QACtD;AAAA,MACF;AAAA,MACA,CAAC,cAAc;AAAA,IACjB;AAGA,iBAAa,cAAc;AAC3B,mBAAe,cAAc;AAE7B,SAAK,QAAQ,CAAC,cAAc,gBAAgB,iBAAiB;AAAA,EAC/D;AAAA,EAEA,IAAI,MAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AACF;;;AC/JO,IAAM,mBAAN,MAAuB;AAAA,EACpB,SAAS,oBAAI,IAAiB;AAAA,EAC9B,mBAAmB,oBAAI,IAA4B;AAAA,EACnD,kBAA+D,CAAC;AAAA,EAChE,oBAAoB,oBAAI,IAAsD;AAAA,EAEtF,OAAO,KAAgB;AACrB,QAAI,KAAK,OAAO,IAAI,IAAI,EAAE,GAAG;AAC3B,YAAM,IAAI,MAAM,gBAAgB,IAAI,EAAE,mCAAmC;AAAA,IAC3E;AACA,SAAK,OAAO,IAAI,IAAI,IAAI,GAAG;AAE3B,UAAM,SAAyB,CAAC;AAChC,WAAO;AAAA,MACL,IAAI,UAAU,CAAC,UAAU;AACvB,mBAAW,WAAW,KAAK,iBAAiB;AAC1C,kBAAQ,OAAO,GAAG;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,MACL,IAAI,GAAG,SAAS,CAAC,UAAU;AACzB,aAAK,cAAc,YAAY,KAAK,KAAK;AAAA,MAC3C,CAAC;AAAA,IACH;AACA,SAAK,iBAAiB,IAAI,IAAI,IAAI,MAAM;AAAA,EAC1C;AAAA,EAEA,OAAO,IAA6B;AAClC,WAAO,KAAK,OAAO,IAAI,EAAE;AAAA,EAC3B;AAAA,EAEA,IAAI,OAAsB;AACxB,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B;AAAA,EAEA,UAAU,IAAkB;AAC1B,UAAM,SAAS,KAAK,iBAAiB,IAAI,EAAE;AAC3C,QAAI,QAAQ;AACV,iBAAW,SAAS,OAAQ,OAAM;AAClC,WAAK,iBAAiB,OAAO,EAAE;AAAA,IACjC;AACA,SAAK,OAAO,OAAO,EAAE;AAAA,EACvB;AAAA,EAEA,SAAyD;AACvD,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AAAA,MAC7C,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,QAAQ,IAAI;AAAA,IACd,EAAE;AAAA,EACJ;AAAA,EAEA,UAAU,SAAwD;AAChE,SAAK,gBAAgB,KAAK,OAAO;AAAA,EACnC;AAAA,EAEA,GAAG,OAAuB,UAA8C;AACtE,UAAM,WAAW,KAAK,kBAAkB,IAAI,KAAK,KAAK,CAAC;AACvD,aAAS,KAAK,QAAQ;AACtB,SAAK,kBAAkB,IAAI,OAAO,QAAQ;AAAA,EAC5C;AAAA,EAEQ,cAAc,UAA0B,MAAuB;AACrE,UAAM,WAAW,KAAK,kBAAkB,IAAI,KAAK;AACjD,QAAI,UAAU;AACZ,iBAAW,WAAW,UAAU;AAC9B,gBAAQ,GAAG,IAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW,IAAY,MAA6B;AAClD,WAAO,IAAI,cAAc,IAAI,IAAI;AAAA,EACnC;AAAA,EAEA,MAAM,gBAA+B;AACnC,UAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,WAAW,CAAC,CAAC;AAAA,EAC5E;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,cAAc;AAAA,EAC3B;AACF;","names":["chunks","text","import_discord"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/bot.ts","../src/event-buffer.ts","../src/message-utils.ts","../src/attachment-utils.ts","../src/onboarding.ts","../src/connector-manager.ts"],"sourcesContent":["// discord-bot-lib — public API\n\nexport { Bot, DEFAULT_SENT_MESSAGE_CACHE_SIZE } from \"./bot.js\";\nexport { BotOnboarding } from \"./onboarding.js\";\nexport { ConnectorManager } from \"./connector-manager.js\";\nexport { EventBuffer } from \"./event-buffer.js\";\nexport {\n chunkMessage,\n DISCORD_MAX_MESSAGE_LENGTH,\n} from \"./message-utils.js\";\nexport {\n MAX_ATTACHMENT_BYTES,\n validateAttachmentSize,\n sanitizeAttachmentName,\n downloadAttachment,\n} from \"./attachment-utils.js\";\n\nexport type {\n AttachmentLike,\n DownloadableAttachment,\n DownloadedAttachment,\n} from \"./attachment-utils.js\";\n\nexport type {\n Author,\n BotConfig,\n BotStatus,\n BotStateAdapter,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n TypingEvent,\n FileAttachment,\n FetchedMessage,\n FetchHistoryOptions,\n OnboardingStep,\n OnboardingStepStatus,\n StepResult,\n ManagerEvents,\n} from \"./types.js\";\n","import { Client, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder, type Message, type Typing } from \"discord.js\";\nimport { stat } from \"node:fs/promises\";\nimport type {\n BotConfig,\n BotStatus,\n FetchedMessage,\n FetchHistoryOptions,\n FileAttachment,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n TypingEvent,\n} from \"./types.js\";\nimport { EventBuffer } from \"./event-buffer.js\";\nimport { chunkMessage } from \"./message-utils.js\";\nimport { validateAttachmentSize, type DownloadableAttachment } from \"./attachment-utils.js\";\n\nexport const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;\n\nexport const DEFAULT_INTENTS = [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n] as const;\n\nexport const DEFAULT_PARTIALS = [Partials.Channel, Partials.Message] as const;\n\ntype MessageHandler = (event: MessageEvent) => void;\ntype TypingHandler = (event: TypingEvent) => void;\ntype ErrorHandler = (error: Error) => void;\n\nexport class Bot {\n readonly id: string;\n readonly name: string;\n\n private _status: BotStatus;\n private readonly client: Client;\n private readonly sentMessages: LimitedCollection<string, SentMessage>;\n private _connectedAt: Date | null = null;\n private readonly messageHandlers: MessageHandler[] = [];\n private readonly typingHandlers: TypingHandler[] = [];\n private readonly errorHandlers: ErrorHandler[] = [];\n private readonly _includeBotMessages: boolean;\n private readonly resolvedIntents: readonly GatewayIntentBits[];\n private typingListenerAttached = false;\n\n private constructor(\n id: string,\n name: string,\n client: Client,\n sentMessageCacheSize: number,\n includeBotMessages: boolean,\n resolvedIntents: readonly GatewayIntentBits[],\n ) {\n this.id = id;\n this.name = name;\n this.client = client;\n this.sentMessages = new LimitedCollection({ maxSize: sentMessageCacheSize });\n this._includeBotMessages = includeBotMessages;\n this.resolvedIntents = resolvedIntents;\n this._status = \"connecting\";\n }\n\n get includeBotMessages(): boolean {\n return this._includeBotMessages;\n }\n\n get status(): BotStatus {\n return this._status;\n }\n\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n get connectedAt(): Date | null {\n return this._connectedAt;\n }\n\n /**\n * Fast path: create a bot from a complete config, connect, verify, and\n * return a ready Bot — or throw with diagnostics.\n */\n static async fromConfig(config: BotConfig): Promise<Bot> {\n const cacheSize = config.sentMessageCacheSize ?? DEFAULT_SENT_MESSAGE_CACHE_SIZE;\n if (cacheSize < 1 || !Number.isInteger(cacheSize)) {\n throw new Error(\n `sentMessageCacheSize must be a positive integer, got ${String(cacheSize)}`,\n );\n }\n\n const includeBotMessages = config.includeBotMessages ?? false;\n const intents = config.intents ?? [...DEFAULT_INTENTS];\n\n const client = new Client({\n intents,\n partials: config.partials ?? [...DEFAULT_PARTIALS],\n });\n\n const bot = new Bot(config.id, config.name, client, cacheSize, includeBotMessages, intents);\n\n await bot.connect(config.token);\n bot.verify();\n bot.setupEventHandlers();\n bot._status = \"ready\";\n bot._connectedAt = new Date();\n\n return bot;\n }\n\n // --- Sending ---\n\n async send(channelId: string, content: MessageContent): Promise<SentMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload) =>\n (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload),\n );\n }\n\n async sendDM(userId: string, content: MessageContent): Promise<SentMessage[]> {\n const user = await this.client.users.fetch(userId);\n return this.sendPayloads(content, (payload) =>\n user.send(payload as Parameters<typeof user.send>[0]),\n );\n }\n\n async reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]> {\n const { channel, message } = await this.fetchMessage(channelId, messageId, \"reply to\");\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload, i) => {\n if (i === 0) {\n return message.reply(payload as Parameters<Message[\"reply\"]>[0]);\n }\n return (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload);\n });\n }\n\n // --- Channel operations ---\n\n async react(channelId: string, messageId: string, emoji: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"react to\");\n try {\n await message.react(emoji);\n } catch (error) {\n throw new Error(\n `Cannot react to message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async editMessage(channelId: string, messageId: string, content: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"edit\");\n try {\n await message.edit(content);\n } catch (error) {\n throw new Error(\n `Cannot edit message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n const channel = await this.fetchTextChannel(channelId);\n const typingChannel = channel as unknown as { sendTyping?: () => Promise<void> };\n if (typeof typingChannel.sendTyping !== \"function\") {\n throw new Error(`Channel ${channelId} does not support typing indicators`);\n }\n try {\n await typingChannel.sendTyping();\n } catch (error) {\n throw new Error(\n `Cannot send typing to channel ${channelId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async sendDMTyping(userId: string): Promise<void> {\n try {\n const user = await this.client.users.fetch(userId);\n const dm = await user.createDM();\n await dm.sendTyping();\n } catch (error) {\n throw new Error(\n `Cannot send typing to user ${userId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n const clamped = Math.max(1, Math.min(limit ?? 20, 100));\n const messages = await channel.messages.fetch({ limit: clamped });\n return [...messages.values()].reverse().map((m) => this.toFetchedMessage(m));\n }\n\n async *fetchHistory(\n channelId: string,\n options: FetchHistoryOptions,\n ): AsyncGenerator<FetchedMessage[]> {\n const { after, before, limit = 500 } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS consumers\n if (after && before) {\n throw new Error('Cannot specify both \"after\" and \"before\"');\n }\n\n let cursor: string;\n let direction: \"after\" | \"before\";\n if (after) {\n cursor = after;\n direction = \"after\";\n } else if (before) {\n cursor = before;\n direction = \"before\";\n } else {\n throw new Error('At least one of \"after\" or \"before\" must be specified');\n }\n\n const effectiveLimit = Math.min(limit, 5000);\n const channel = await this.fetchTextChannel(channelId);\n\n let totalFetched = 0;\n\n while (totalFetched < effectiveLimit) {\n const pageSize = Math.min(100, effectiveLimit - totalFetched);\n\n const fetched = await this.fetchPageWithRetry(channel, {\n limit: pageSize,\n [direction]: cursor,\n });\n\n if (fetched.size === 0) break;\n\n const rawMessages = [...fetched.values()];\n const messages = rawMessages.map((m) => this.toFetchedMessage(m));\n\n yield messages;\n totalFetched += messages.length;\n\n const lastMessage = rawMessages.at(-1);\n if (!lastMessage) break;\n cursor = lastMessage.id;\n\n if (fetched.size < pageSize) break;\n }\n }\n\n async getMessageAttachments(\n channelId: string,\n messageId: string,\n ): Promise<DownloadableAttachment[]> {\n const { message } = await this.fetchMessage(channelId, messageId, \"fetch attachments for\");\n return [...message.attachments.values()].map((att) => ({\n id: att.id,\n size: att.size,\n name: att.name,\n url: att.url,\n contentType: att.contentType,\n }));\n }\n\n // --- Receiving (event-driven) ---\n\n onMessage(handler: MessageHandler): () => void {\n this.messageHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.messageHandlers.indexOf(handler);\n if (index !== -1) this.messageHandlers.splice(index, 1);\n };\n }\n\n onTypingStart(handler: TypingHandler): () => void {\n const hasGuildTyping = this.resolvedIntents.includes(GatewayIntentBits.GuildMessageTyping);\n const hasDMTyping = this.resolvedIntents.includes(GatewayIntentBits.DirectMessageTyping);\n if (!hasGuildTyping && !hasDMTyping) {\n throw new Error(\n \"onTypingStart requires GuildMessageTyping and/or DirectMessageTyping intents. \" +\n \"Add them to the intents array in BotConfig.\",\n );\n }\n\n if (!this.typingListenerAttached) {\n this.typingListenerAttached = true;\n this.client.on(Events.TypingStart, (typing: Typing) => {\n try {\n const event = this.toTypingEvent(typing);\n for (const h of this.typingHandlers) {\n h(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n }\n\n this.typingHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.typingHandlers.indexOf(handler);\n if (index !== -1) this.typingHandlers.splice(index, 1);\n };\n }\n\n // --- Receiving (async iterable) ---\n\n messages(options?: { filter?: MessageFilter }): AsyncIterable<MessageEvent> {\n const cleanup: { unsub?: () => void } = {};\n const buffer = new EventBuffer<MessageEvent>({\n onClose: () => cleanup.unsub?.(),\n });\n cleanup.unsub = this.onMessage((event) => {\n if (options?.filter && !options.filter(event)) return;\n buffer.push(event);\n });\n return buffer;\n }\n\n // --- Error handling ---\n\n on(_event: \"error\", handler: ErrorHandler): () => void {\n this.errorHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.errorHandlers.indexOf(handler);\n if (index !== -1) this.errorHandlers.splice(index, 1);\n };\n }\n\n private emitError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n // --- Lifecycle ---\n\n async disconnect(): Promise<void> {\n this.client.removeAllListeners();\n await this.client.destroy();\n this.messageHandlers.length = 0;\n this.typingHandlers.length = 0;\n this.typingListenerAttached = false;\n this.errorHandlers.length = 0;\n this._status = \"disconnected\";\n this._connectedAt = null;\n }\n\n // --- Private ---\n\n private async fetchTextChannel(channelId: string) {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) {\n throw new Error(\n `Channel ${channelId} is not a text-based channel or does not exist`,\n );\n }\n return channel;\n }\n\n private async fetchMessage(\n channelId: string,\n messageId: string,\n verb: string,\n ): Promise<{ channel: Awaited<ReturnType<Bot[\"fetchTextChannel\"]>>; message: Message }> {\n const channel = await this.fetchTextChannel(channelId);\n try {\n const message = await channel.messages.fetch(messageId);\n return { channel, message };\n } catch (error) {\n throw new Error(\n `Cannot ${verb} message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private toFetchedMessage(msg: Message): FetchedMessage {\n return {\n messageId: msg.id,\n channelId: msg.channelId,\n author: {\n id: msg.author.id,\n username: msg.author.username,\n bot: msg.author.bot,\n },\n content: msg.content,\n timestamp: msg.createdAt,\n attachmentCount: msg.attachments.size,\n };\n }\n\n private async fetchPageWithRetry(\n channel: { messages: { fetch(opts: unknown): Promise<Map<string, Message>> } },\n options: Record<string, unknown>,\n maxRetryMs = 5000,\n ): Promise<Map<string, Message>> {\n try {\n return await channel.messages.fetch(options);\n } catch (err) {\n if (err instanceof Error && \"retryAfter\" in err) {\n const { retryAfter } = err;\n if (typeof retryAfter === \"number\" && retryAfter <= maxRetryMs) {\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n return channel.messages.fetch(options);\n }\n }\n throw err;\n }\n }\n\n private async connect(token: string): Promise<void> {\n this._status = \"connecting\";\n await this.client.login(token);\n }\n\n private verify(): void {\n this._status = \"verifying\";\n\n if (this.client.guilds.cache.size === 0) {\n this._status = \"failed\";\n throw new Error(\n \"Bot is not in any guild. Invite the bot to at least one server before connecting.\",\n );\n }\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.MessageCreate, (message: Message) => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null with Partials.Message\n if (!message.author) return;\n // Always ignore own messages (prevent infinite loops)\n if (message.author.id === this.client.user?.id) return;\n // Ignore other bots unless opted in\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n\n // Workaround: discord.js v14 silently drops messageCreate for DM channels\n // not in cache — MessageCreateAction.getChannel() fails to construct a\n // DMChannel when the raw payload lacks a `type` field. Catch these here.\n this.client.on(\"raw\" as string, (packet: { t: string; d: { id?: string; channel_id?: string; guild_id?: string } }) => {\n if (packet.t !== \"MESSAGE_CREATE\") return;\n if (packet.d.guild_id) return;\n const channelId = packet.d.channel_id;\n const messageId = packet.d.id;\n if (!channelId || !messageId) return;\n if (this.client.channels.cache.has(channelId)) return;\n\n void this.handleUncachedDM(channelId, messageId);\n });\n }\n\n private async handleUncachedDM(channelId: string, messageId: string): Promise<void> {\n try {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) return;\n\n const message = await channel.messages.fetch(messageId);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null with Partials.Message\n if (!message.author) return;\n if (message.author.id === this.client.user?.id) return;\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n }\n\n private toMessageEvent(message: Message): MessageEvent {\n const isDM = message.guildId === null;\n const botUser = this.client.user;\n const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);\n\n let isReply = false;\n let replyTo: SentMessage | null = null;\n if (message.reference?.messageId) {\n const original = this.sentMessages.get(message.reference.messageId);\n if (original) {\n isReply = true;\n replyTo = original;\n }\n }\n\n return {\n messageId: message.id,\n author: {\n id: message.author.id,\n username: message.author.username,\n bot: message.author.bot,\n },\n content: message.content,\n channelId: message.channelId,\n guildId: message.guildId ?? null,\n timestamp: message.createdAt,\n mentions: [...message.mentions.users.keys()],\n raw: message,\n isDM,\n isMention,\n isReply,\n replyTo,\n };\n }\n\n private toTypingEvent(typing: Typing): TypingEvent {\n return {\n channelId: typing.channel.id,\n userId: typing.user.id,\n startedAt: typing.startedAt,\n guildId: typing.guild?.id ?? null,\n raw: typing,\n };\n }\n\n private async sendPayloads(\n content: MessageContent,\n dispatcher: (payload: ChunkedPayload, index: number) => Promise<Message>,\n ): Promise<SentMessage[]> {\n const files =\n typeof content !== \"string\" && content.files?.length\n ? await validateAndBuildAttachments(content.files)\n : undefined;\n const payloads = toChunkedPayloads(content, files);\n const results: SentMessage[] = [];\n let i = 0;\n for (const payload of payloads) {\n const msg = await dispatcher(payload, i++);\n results.push(this.trackSentMessage(msg));\n }\n return results;\n }\n\n private trackSentMessage(message: Message): SentMessage {\n const sent: SentMessage = {\n messageId: message.id,\n channelId: message.channelId,\n timestamp: message.createdAt,\n raw: message,\n };\n this.sentMessages.set(sent.messageId, sent);\n return sent;\n }\n}\n\ninterface ChunkedPayload {\n content?: string;\n embeds?: unknown[];\n files?: AttachmentBuilder[];\n}\n\nfunction toChunkedPayloads(\n content: MessageContent,\n files?: AttachmentBuilder[],\n): ChunkedPayload[] {\n if (typeof content === \"string\") {\n const chunks = chunkMessage(content);\n return chunks.map((text, i) => ({\n content: text,\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n }\n const text = content.content ?? \"\";\n const chunks = chunkMessage(text);\n if (chunks.length === 0) {\n return [{ embeds: content.embeds, ...(files?.length ? { files } : {}) }];\n }\n return chunks.map((chunk, i) => ({\n content: chunk,\n ...(i === 0 && content.embeds ? { embeds: content.embeds } : {}),\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n}\n\nconst MAX_FILES_PER_MESSAGE = 10;\n\nasync function validateAndBuildAttachments(\n files: FileAttachment[],\n): Promise<AttachmentBuilder[]> {\n if (files.length > MAX_FILES_PER_MESSAGE) {\n throw new Error(\n `Too many attachments: ${String(files.length)} exceeds Discord's limit of ${String(MAX_FILES_PER_MESSAGE)} attachments per message`,\n );\n }\n\n const sizes = await Promise.all(\n files.map((file) =>\n typeof file.data === \"string\"\n ? stat(file.data).then((st) => st.size)\n : Promise.resolve(file.data.byteLength),\n ),\n );\n\n return files.map((file, i) => {\n const size = sizes[i];\n if (size === undefined) throw new Error(`Missing size for attachment: ${file.name}`);\n validateAttachmentSize({ size, name: file.name, id: file.name });\n return new AttachmentBuilder(file.data, { name: file.name });\n });\n}\n","/**\n * Single-consumer async iterable buffer. Each call to messages() on a Bot\n * creates its own buffer. Do not share a single EventBuffer between\n * multiple for-await consumers.\n */\nexport class EventBuffer<T> implements AsyncIterable<T> {\n private buffer: T[] = [];\n private resolve: ((value: IteratorResult<T>) => void) | null = null;\n private closed = false;\n private readonly onClose: (() => void) | undefined;\n\n constructor(options?: { onClose?: () => void }) {\n this.onClose = options?.onClose;\n }\n\n push(value: T): void {\n if (this.closed) return;\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value, done: false });\n } else {\n this.buffer.push(value);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.onClose?.();\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value: undefined as unknown as T, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.buffer.length > 0) {\n // Length check guarantees shift() returns a value\n const value = this.buffer.shift() as T;\n return Promise.resolve({ value, done: false });\n }\n if (this.closed) {\n return Promise.resolve({\n value: undefined as unknown as T,\n done: true,\n });\n }\n return new Promise((resolve) => {\n this.resolve = resolve;\n });\n },\n return: (): Promise<IteratorResult<T>> => {\n this.close();\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n },\n };\n }\n}\n","// Message utility functions for discord-bot-lib\n\n/** Discord's hard limit on message length. */\nexport const DISCORD_MAX_MESSAGE_LENGTH = 2000;\n\n/**\n * Splits text into chunks that fit within Discord's message length limit.\n * Prefers splitting at paragraph boundaries (\\n\\n), then line breaks (\\n),\n * then spaces, then hard-cuts as a last resort.\n *\n * @param text - The text to split\n * @param limit - Maximum characters per chunk (default: {@link DISCORD_MAX_MESSAGE_LENGTH})\n * @returns Array of text chunks, each within the limit\n */\nexport function chunkMessage(\n text: string,\n limit: number = DISCORD_MAX_MESSAGE_LENGTH,\n): string[] {\n if (!text) return [];\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const halfLimit = limit / 2;\n let rest = text;\n\n while (rest.length > limit) {\n const para = rest.lastIndexOf(\"\\n\\n\", limit);\n const line = rest.lastIndexOf(\"\\n\", limit);\n const space = rest.lastIndexOf(\" \", limit);\n const cut =\n para > halfLimit\n ? para\n : line > halfLimit\n ? line\n : space > 0\n ? space\n : limit;\n\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).replace(/^\\n+/, \"\");\n }\n\n if (rest) chunks.push(rest);\n\n return chunks;\n}\n","// Attachment utility functions for discord-bot-lib\n\n/** Maximum attachment size in bytes (25 MB). Matches Discord's default file size limit. */\nexport const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024; // 26_214_400\n\n/** Shape compatible with discord.js Attachment for attachment utilities. */\nexport interface AttachmentLike {\n size: number;\n name?: string | null;\n id: string;\n}\n\n/**\n * Validates that an attachment does not exceed Discord's size limit.\n * Designed for use in both upload (sending files) and download paths.\n *\n * @throws Error if the attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n */\nexport function validateAttachmentSize(attachment: AttachmentLike): void {\n if (attachment.size > MAX_ATTACHMENT_BYTES) {\n const actualMB = (attachment.size / 1024 / 1024).toFixed(1);\n const limitMB = MAX_ATTACHMENT_BYTES / 1024 / 1024;\n const nameInfo = attachment.name ? `: ${attachment.name}` : \"\";\n throw new Error(\n `Attachment too large (${actualMB} MB > ${String(limitMB)} MB limit)${nameInfo}`,\n );\n }\n}\n\n/**\n * Sanitizes an attachment filename by replacing unsafe characters\n * (`[`, `]`, `\\r`, `\\n`, `;`) with underscores. Falls back to the\n * attachment ID when the name is missing or empty after sanitization.\n */\nconst UNSAFE_CHARS = /[[\\]\\r\\n;]/g;\n\nexport function sanitizeAttachmentName(\n attachment: Pick<AttachmentLike, \"name\" | \"id\">,\n): string {\n const raw = attachment.name;\n if (!raw || raw.replace(UNSAFE_CHARS, \"\").length === 0) {\n return attachment.id;\n }\n return raw.replace(UNSAFE_CHARS, \"_\");\n}\n\n/** Result of downloading a Discord attachment. */\nexport interface DownloadedAttachment {\n buffer: Buffer;\n filename: string;\n contentType: string;\n}\n\n/** Shape accepted by {@link downloadAttachment}. Compatible with discord.js `Attachment`. */\nexport interface DownloadableAttachment extends AttachmentLike {\n url: string;\n contentType?: string | null;\n}\n\n/**\n * Downloads a Discord message attachment to a raw buffer.\n * Validates size and sanitizes filename before returning.\n *\n * @throws Error if attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n * @throws Error if the fetch response is not ok\n */\nexport async function downloadAttachment(\n attachment: DownloadableAttachment,\n): Promise<DownloadedAttachment> {\n validateAttachmentSize(attachment);\n\n const res = await fetch(attachment.url);\n if (!res.ok) {\n throw new Error(\n `Failed to download attachment: ${String(res.status)} ${res.statusText}`,\n );\n }\n\n const ab = await res.arrayBuffer();\n const buffer = Buffer.from(ab, 0, ab.byteLength);\n const filename = sanitizeAttachmentName(attachment);\n const contentType = attachment.contentType ?? \"application/octet-stream\";\n\n return { buffer, filename, contentType };\n}\n","import { Client } from \"discord.js\";\nimport type { OnboardingStep, OnboardingStepStatus, StepResult } from \"./types.js\";\nimport { Bot, DEFAULT_INTENTS, DEFAULT_PARTIALS } from \"./bot.js\";\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\ntype StepAction = (...args: unknown[]) => Promise<StepResult>;\n\nclass OnboardingStepImpl implements OnboardingStep {\n id: string;\n label: string;\n instructions: string;\n status: OnboardingStepStatus;\n error?: string;\n nextSibling?: OnboardingStepImpl;\n\n private readonly _action: StepAction;\n private readonly _dependencies: OnboardingStepImpl[];\n\n constructor(\n id: string,\n label: string,\n instructions: string,\n action: StepAction,\n dependencies: OnboardingStepImpl[] = [],\n initialStatus: OnboardingStepStatus = \"pending\",\n ) {\n this.id = id;\n this.label = label;\n this.instructions = instructions;\n this._action = action;\n this._dependencies = dependencies;\n this.status = initialStatus;\n }\n\n async complete(...args: unknown[]): Promise<StepResult> {\n // Check dependencies\n for (const dep of this._dependencies) {\n if (dep.status !== \"completed\") {\n const error = `Cannot complete '${this.id}': dependency '${dep.id}' is not completed.`;\n this.status = \"failed\";\n this.error = error;\n return { success: false, error };\n }\n }\n\n try {\n const result = await this._action(...args);\n if (result.success) {\n this.status = \"completed\";\n this.error = undefined;\n // Promote next step to ready\n if (this.nextSibling?.status === \"pending\") {\n this.nextSibling.status = \"ready\";\n result.nextStep = this.nextSibling;\n }\n } else {\n this.status = \"failed\";\n this.error = result.error;\n }\n return result;\n } catch (err) {\n const message = toErrorMessage(err);\n this.status = \"failed\";\n this.error = message;\n return { success: false, error: message };\n }\n }\n}\n\nexport class BotOnboarding {\n readonly id: string;\n readonly name: string;\n readonly steps: OnboardingStep[];\n\n private _bot: Bot | undefined;\n private _token: string | undefined;\n\n constructor(id: string, name: string) {\n this.id = id;\n this.name = name;\n const provideToken = new OnboardingStepImpl(\n \"provide_token\",\n \"Provide Bot Token\",\n \"Enter your Discord bot token. You can find this in the Discord Developer Portal under your application's Bot settings.\",\n async (...args: unknown[]) => {\n const token = args[0] as string;\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(token);\n await client.destroy();\n this._token = token;\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [],\n \"ready\",\n );\n\n const inviteToServer = new OnboardingStepImpl(\n \"invite_to_server\",\n \"Invite Bot to Server\",\n \"Add the bot to at least one Discord server using the OAuth2 URL from the Developer Portal.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(this._token);\n if (client.guilds.cache.size === 0) {\n await client.destroy();\n return { success: false, error: \"Bot is not in any server yet\" };\n }\n await client.destroy();\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [provideToken],\n );\n\n const verifyPermissions = new OnboardingStepImpl(\n \"verify_permissions\",\n \"Verify Permissions\",\n \"Verify the bot has the required permissions in your server.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n try {\n this._bot = await Bot.fromConfig({\n id: this.id,\n name: this.name,\n token: this._token,\n });\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [inviteToServer],\n );\n\n // Wire nextSibling chain for step promotion\n provideToken.nextSibling = inviteToServer;\n inviteToServer.nextSibling = verifyPermissions;\n\n this.steps = [provideToken, inviteToServer, verifyPermissions];\n }\n\n get bot(): Bot | undefined {\n return this._bot;\n }\n}\n","import { Bot } from \"./bot.js\";\nimport { BotOnboarding } from \"./onboarding.js\";\nimport type { MessageEvent, TypingEvent } from \"./types.js\";\n\ntype LifecycleEvent = \"botReady\" | \"botDisconnected\" | \"botError\";\n\nexport class ConnectorManager {\n private botMap = new Map<string, Bot>();\n private botUnsubscribers = new Map<string, (() => void)[]>();\n private messageHandlers: ((event: MessageEvent, bot: Bot) => void)[] = [];\n private typingHandlers: ((event: TypingEvent, bot: Bot) => void)[] = [];\n private lifecycleHandlers = new Map<LifecycleEvent, ((...args: unknown[]) => void)[]>();\n\n addBot(bot: Bot): void {\n if (this.botMap.has(bot.id)) {\n throw new Error(`Bot with id \"${bot.id}\" already exists in this manager.`);\n }\n this.botMap.set(bot.id, bot);\n\n const unsubs: (() => void)[] = [];\n unsubs.push(\n bot.onMessage((event) => {\n for (const handler of this.messageHandlers) {\n handler(event, bot);\n }\n }),\n );\n unsubs.push(\n bot.on(\"error\", (error) => {\n this.emitLifecycle(\"botError\", bot, error);\n }),\n );\n if (this.typingHandlers.length > 0) {\n this.subscribeTyping(bot, unsubs);\n }\n this.botUnsubscribers.set(bot.id, unsubs);\n }\n\n getBot(id: string): Bot | undefined {\n return this.botMap.get(id);\n }\n\n get bots(): Iterable<Bot> {\n return this.botMap.values();\n }\n\n removeBot(id: string): void {\n const unsubs = this.botUnsubscribers.get(id);\n if (unsubs) {\n for (const unsub of unsubs) unsub();\n this.botUnsubscribers.delete(id);\n }\n this.botMap.delete(id);\n }\n\n status(): { id: string; name: string; status: string }[] {\n return [...this.botMap.values()].map((bot) => ({\n id: bot.id,\n name: bot.name,\n status: bot.status,\n }));\n }\n\n onMessage(handler: (event: MessageEvent, bot: Bot) => void): void {\n this.messageHandlers.push(handler);\n }\n\n onTypingStart(handler: (event: TypingEvent, bot: Bot) => void): void {\n const isFirst = this.typingHandlers.length === 0;\n this.typingHandlers.push(handler);\n if (isFirst) {\n for (const [id, bot] of this.botMap) {\n const unsubs = this.botUnsubscribers.get(id) ?? [];\n this.subscribeTyping(bot, unsubs);\n }\n }\n }\n\n on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void {\n const handlers = this.lifecycleHandlers.get(event) ?? [];\n handlers.push(callback);\n this.lifecycleHandlers.set(event, handlers);\n }\n\n private subscribeTyping(bot: Bot, unsubs: (() => void)[]): void {\n try {\n unsubs.push(\n bot.onTypingStart((event) => {\n for (const handler of this.typingHandlers) {\n handler(event, bot);\n }\n }),\n );\n } catch (error) {\n // Re-throw unexpected errors; only suppress the known intent-check error\n const isIntentError = error instanceof Error && error.message.startsWith(\"onTypingStart requires\");\n if (!isIntentError) throw error;\n }\n }\n\n private emitLifecycle(event: LifecycleEvent, ...args: unknown[]): void {\n const handlers = this.lifecycleHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n handler(...args);\n }\n }\n }\n\n onboardBot(id: string, name: string): BotOnboarding {\n return new BotOnboarding(id, name);\n }\n\n async disconnectAll(): Promise<void> {\n await Promise.all([...this.botMap.values()].map((bot) => bot.disconnect()));\n }\n\n async shutdown(): Promise<void> {\n await this.disconnectAll();\n }\n}\n"],"mappings":"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","names":["chunks","text","import_discord"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { GatewayIntentBits, Partials, Message } from 'discord.js';
1
+ import { GatewayIntentBits, Partials, Message, Typing } from 'discord.js';
2
2
 
3
3
  type BotStatus = "unregistered" | "configuring" | "connecting" | "verifying" | "ready" | "disconnected" | "failed";
4
4
  interface BotStateAdapter {
@@ -76,6 +76,13 @@ type FetchHistoryOptions = {
76
76
  limit?: number;
77
77
  };
78
78
  type MessageFilter = (message: MessageEvent) => boolean;
79
+ interface TypingEvent {
80
+ channelId: string;
81
+ userId: string;
82
+ startedAt: Date;
83
+ guildId: string | null;
84
+ raw: Typing;
85
+ }
79
86
  type OnboardingStepStatus = "pending" | "ready" | "completed" | "failed";
80
87
  interface StepResult {
81
88
  success: boolean;
@@ -143,6 +150,7 @@ declare function downloadAttachment(attachment: DownloadableAttachment): Promise
143
150
 
144
151
  declare const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;
145
152
  type MessageHandler = (event: MessageEvent) => void;
153
+ type TypingHandler = (event: TypingEvent) => void;
146
154
  type ErrorHandler = (error: Error) => void;
147
155
  declare class Bot {
148
156
  readonly id: string;
@@ -152,8 +160,11 @@ declare class Bot {
152
160
  private readonly sentMessages;
153
161
  private _connectedAt;
154
162
  private readonly messageHandlers;
163
+ private readonly typingHandlers;
155
164
  private readonly errorHandlers;
156
165
  private readonly _includeBotMessages;
166
+ private readonly resolvedIntents;
167
+ private typingListenerAttached;
157
168
  private constructor();
158
169
  get includeBotMessages(): boolean;
159
170
  get status(): BotStatus;
@@ -169,10 +180,13 @@ declare class Bot {
169
180
  reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]>;
170
181
  react(channelId: string, messageId: string, emoji: string): Promise<void>;
171
182
  editMessage(channelId: string, messageId: string, content: string): Promise<void>;
183
+ sendTyping(channelId: string): Promise<void>;
184
+ sendDMTyping(userId: string): Promise<void>;
172
185
  fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]>;
173
186
  fetchHistory(channelId: string, options: FetchHistoryOptions): AsyncGenerator<FetchedMessage[]>;
174
187
  getMessageAttachments(channelId: string, messageId: string): Promise<DownloadableAttachment[]>;
175
188
  onMessage(handler: MessageHandler): () => void;
189
+ onTypingStart(handler: TypingHandler): () => void;
176
190
  messages(options?: {
177
191
  filter?: MessageFilter;
178
192
  }): AsyncIterable<MessageEvent>;
@@ -186,7 +200,9 @@ declare class Bot {
186
200
  private connect;
187
201
  private verify;
188
202
  private setupEventHandlers;
203
+ private handleUncachedDM;
189
204
  private toMessageEvent;
205
+ private toTypingEvent;
190
206
  private sendPayloads;
191
207
  private trackSentMessage;
192
208
  }
@@ -206,6 +222,7 @@ declare class ConnectorManager {
206
222
  private botMap;
207
223
  private botUnsubscribers;
208
224
  private messageHandlers;
225
+ private typingHandlers;
209
226
  private lifecycleHandlers;
210
227
  addBot(bot: Bot): void;
211
228
  getBot(id: string): Bot | undefined;
@@ -217,7 +234,9 @@ declare class ConnectorManager {
217
234
  status: string;
218
235
  }[];
219
236
  onMessage(handler: (event: MessageEvent, bot: Bot) => void): void;
237
+ onTypingStart(handler: (event: TypingEvent, bot: Bot) => void): void;
220
238
  on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void;
239
+ private subscribeTyping;
221
240
  private emitLifecycle;
222
241
  onboardBot(id: string, name: string): BotOnboarding;
223
242
  disconnectAll(): Promise<void>;
@@ -255,4 +274,4 @@ declare const DISCORD_MAX_MESSAGE_LENGTH = 2000;
255
274
  */
256
275
  declare function chunkMessage(text: string, limit?: number): string[];
257
276
 
258
- export { type AttachmentLike, type Author, Bot, type BotConfig, BotOnboarding, type BotStateAdapter, type BotStatus, ConnectorManager, DEFAULT_SENT_MESSAGE_CACHE_SIZE, DISCORD_MAX_MESSAGE_LENGTH, type DownloadableAttachment, type DownloadedAttachment, EventBuffer, type FetchHistoryOptions, type FetchedMessage, type FileAttachment, MAX_ATTACHMENT_BYTES, type ManagerEvents, type MessageContent, type MessageEvent, type MessageFilter, type OnboardingStep, type OnboardingStepStatus, type SentMessage, type StepResult, chunkMessage, downloadAttachment, sanitizeAttachmentName, validateAttachmentSize };
277
+ export { type AttachmentLike, type Author, Bot, type BotConfig, BotOnboarding, type BotStateAdapter, type BotStatus, ConnectorManager, DEFAULT_SENT_MESSAGE_CACHE_SIZE, DISCORD_MAX_MESSAGE_LENGTH, type DownloadableAttachment, type DownloadedAttachment, EventBuffer, type FetchHistoryOptions, type FetchedMessage, type FileAttachment, MAX_ATTACHMENT_BYTES, type ManagerEvents, type MessageContent, type MessageEvent, type MessageFilter, type OnboardingStep, type OnboardingStepStatus, type SentMessage, type StepResult, type TypingEvent, chunkMessage, downloadAttachment, sanitizeAttachmentName, validateAttachmentSize };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GatewayIntentBits, Partials, Message } from 'discord.js';
1
+ import { GatewayIntentBits, Partials, Message, Typing } from 'discord.js';
2
2
 
3
3
  type BotStatus = "unregistered" | "configuring" | "connecting" | "verifying" | "ready" | "disconnected" | "failed";
4
4
  interface BotStateAdapter {
@@ -76,6 +76,13 @@ type FetchHistoryOptions = {
76
76
  limit?: number;
77
77
  };
78
78
  type MessageFilter = (message: MessageEvent) => boolean;
79
+ interface TypingEvent {
80
+ channelId: string;
81
+ userId: string;
82
+ startedAt: Date;
83
+ guildId: string | null;
84
+ raw: Typing;
85
+ }
79
86
  type OnboardingStepStatus = "pending" | "ready" | "completed" | "failed";
80
87
  interface StepResult {
81
88
  success: boolean;
@@ -143,6 +150,7 @@ declare function downloadAttachment(attachment: DownloadableAttachment): Promise
143
150
 
144
151
  declare const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;
145
152
  type MessageHandler = (event: MessageEvent) => void;
153
+ type TypingHandler = (event: TypingEvent) => void;
146
154
  type ErrorHandler = (error: Error) => void;
147
155
  declare class Bot {
148
156
  readonly id: string;
@@ -152,8 +160,11 @@ declare class Bot {
152
160
  private readonly sentMessages;
153
161
  private _connectedAt;
154
162
  private readonly messageHandlers;
163
+ private readonly typingHandlers;
155
164
  private readonly errorHandlers;
156
165
  private readonly _includeBotMessages;
166
+ private readonly resolvedIntents;
167
+ private typingListenerAttached;
157
168
  private constructor();
158
169
  get includeBotMessages(): boolean;
159
170
  get status(): BotStatus;
@@ -169,10 +180,13 @@ declare class Bot {
169
180
  reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]>;
170
181
  react(channelId: string, messageId: string, emoji: string): Promise<void>;
171
182
  editMessage(channelId: string, messageId: string, content: string): Promise<void>;
183
+ sendTyping(channelId: string): Promise<void>;
184
+ sendDMTyping(userId: string): Promise<void>;
172
185
  fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]>;
173
186
  fetchHistory(channelId: string, options: FetchHistoryOptions): AsyncGenerator<FetchedMessage[]>;
174
187
  getMessageAttachments(channelId: string, messageId: string): Promise<DownloadableAttachment[]>;
175
188
  onMessage(handler: MessageHandler): () => void;
189
+ onTypingStart(handler: TypingHandler): () => void;
176
190
  messages(options?: {
177
191
  filter?: MessageFilter;
178
192
  }): AsyncIterable<MessageEvent>;
@@ -186,7 +200,9 @@ declare class Bot {
186
200
  private connect;
187
201
  private verify;
188
202
  private setupEventHandlers;
203
+ private handleUncachedDM;
189
204
  private toMessageEvent;
205
+ private toTypingEvent;
190
206
  private sendPayloads;
191
207
  private trackSentMessage;
192
208
  }
@@ -206,6 +222,7 @@ declare class ConnectorManager {
206
222
  private botMap;
207
223
  private botUnsubscribers;
208
224
  private messageHandlers;
225
+ private typingHandlers;
209
226
  private lifecycleHandlers;
210
227
  addBot(bot: Bot): void;
211
228
  getBot(id: string): Bot | undefined;
@@ -217,7 +234,9 @@ declare class ConnectorManager {
217
234
  status: string;
218
235
  }[];
219
236
  onMessage(handler: (event: MessageEvent, bot: Bot) => void): void;
237
+ onTypingStart(handler: (event: TypingEvent, bot: Bot) => void): void;
220
238
  on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void;
239
+ private subscribeTyping;
221
240
  private emitLifecycle;
222
241
  onboardBot(id: string, name: string): BotOnboarding;
223
242
  disconnectAll(): Promise<void>;
@@ -255,4 +274,4 @@ declare const DISCORD_MAX_MESSAGE_LENGTH = 2000;
255
274
  */
256
275
  declare function chunkMessage(text: string, limit?: number): string[];
257
276
 
258
- export { type AttachmentLike, type Author, Bot, type BotConfig, BotOnboarding, type BotStateAdapter, type BotStatus, ConnectorManager, DEFAULT_SENT_MESSAGE_CACHE_SIZE, DISCORD_MAX_MESSAGE_LENGTH, type DownloadableAttachment, type DownloadedAttachment, EventBuffer, type FetchHistoryOptions, type FetchedMessage, type FileAttachment, MAX_ATTACHMENT_BYTES, type ManagerEvents, type MessageContent, type MessageEvent, type MessageFilter, type OnboardingStep, type OnboardingStepStatus, type SentMessage, type StepResult, chunkMessage, downloadAttachment, sanitizeAttachmentName, validateAttachmentSize };
277
+ export { type AttachmentLike, type Author, Bot, type BotConfig, BotOnboarding, type BotStateAdapter, type BotStatus, ConnectorManager, DEFAULT_SENT_MESSAGE_CACHE_SIZE, DISCORD_MAX_MESSAGE_LENGTH, type DownloadableAttachment, type DownloadedAttachment, EventBuffer, type FetchHistoryOptions, type FetchedMessage, type FileAttachment, MAX_ATTACHMENT_BYTES, type ManagerEvents, type MessageContent, type MessageEvent, type MessageFilter, type OnboardingStep, type OnboardingStepStatus, type SentMessage, type StepResult, type TypingEvent, chunkMessage, downloadAttachment, sanitizeAttachmentName, validateAttachmentSize };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/bot.ts
2
- import { Client, ChannelType, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder } from "discord.js";
2
+ import { Client, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder } from "discord.js";
3
3
  import { stat } from "fs/promises";
4
4
 
5
5
  // src/event-buffer.ts
@@ -128,14 +128,18 @@ var Bot = class _Bot {
128
128
  sentMessages;
129
129
  _connectedAt = null;
130
130
  messageHandlers = [];
131
+ typingHandlers = [];
131
132
  errorHandlers = [];
132
133
  _includeBotMessages;
133
- constructor(id, name, client, sentMessageCacheSize, includeBotMessages) {
134
+ resolvedIntents;
135
+ typingListenerAttached = false;
136
+ constructor(id, name, client, sentMessageCacheSize, includeBotMessages, resolvedIntents) {
134
137
  this.id = id;
135
138
  this.name = name;
136
139
  this.client = client;
137
140
  this.sentMessages = new LimitedCollection({ maxSize: sentMessageCacheSize });
138
141
  this._includeBotMessages = includeBotMessages;
142
+ this.resolvedIntents = resolvedIntents;
139
143
  this._status = "connecting";
140
144
  }
141
145
  get includeBotMessages() {
@@ -162,11 +166,12 @@ var Bot = class _Bot {
162
166
  );
163
167
  }
164
168
  const includeBotMessages = config.includeBotMessages ?? false;
169
+ const intents = config.intents ?? [...DEFAULT_INTENTS];
165
170
  const client = new Client({
166
- intents: config.intents ?? [...DEFAULT_INTENTS],
171
+ intents,
167
172
  partials: config.partials ?? [...DEFAULT_PARTIALS]
168
173
  });
169
- const bot = new _Bot(config.id, config.name, client, cacheSize, includeBotMessages);
174
+ const bot = new _Bot(config.id, config.name, client, cacheSize, includeBotMessages, intents);
170
175
  await bot.connect(config.token);
171
176
  bot.verify();
172
177
  bot.setupEventHandlers();
@@ -225,6 +230,31 @@ var Bot = class _Bot {
225
230
  );
226
231
  }
227
232
  }
233
+ async sendTyping(channelId) {
234
+ const channel = await this.fetchTextChannel(channelId);
235
+ const typingChannel = channel;
236
+ if (typeof typingChannel.sendTyping !== "function") {
237
+ throw new Error(`Channel ${channelId} does not support typing indicators`);
238
+ }
239
+ try {
240
+ await typingChannel.sendTyping();
241
+ } catch (error) {
242
+ throw new Error(
243
+ `Cannot send typing to channel ${channelId}: ${error instanceof Error ? error.message : String(error)}`
244
+ );
245
+ }
246
+ }
247
+ async sendDMTyping(userId) {
248
+ try {
249
+ const user = await this.client.users.fetch(userId);
250
+ const dm = await user.createDM();
251
+ await dm.sendTyping();
252
+ } catch (error) {
253
+ throw new Error(
254
+ `Cannot send typing to user ${userId}: ${error instanceof Error ? error.message : String(error)}`
255
+ );
256
+ }
257
+ }
228
258
  async fetchMessages(channelId, limit) {
229
259
  const channel = await this.fetchTextChannel(channelId);
230
260
  const clamped = Math.max(1, Math.min(limit ?? 20, 100));
@@ -288,6 +318,36 @@ var Bot = class _Bot {
288
318
  if (index !== -1) this.messageHandlers.splice(index, 1);
289
319
  };
290
320
  }
321
+ onTypingStart(handler) {
322
+ const hasGuildTyping = this.resolvedIntents.includes(GatewayIntentBits.GuildMessageTyping);
323
+ const hasDMTyping = this.resolvedIntents.includes(GatewayIntentBits.DirectMessageTyping);
324
+ if (!hasGuildTyping && !hasDMTyping) {
325
+ throw new Error(
326
+ "onTypingStart requires GuildMessageTyping and/or DirectMessageTyping intents. Add them to the intents array in BotConfig."
327
+ );
328
+ }
329
+ if (!this.typingListenerAttached) {
330
+ this.typingListenerAttached = true;
331
+ this.client.on(Events.TypingStart, (typing) => {
332
+ try {
333
+ const event = this.toTypingEvent(typing);
334
+ for (const h of this.typingHandlers) {
335
+ h(event);
336
+ }
337
+ } catch (error) {
338
+ this.emitError(error);
339
+ }
340
+ });
341
+ }
342
+ this.typingHandlers.push(handler);
343
+ let removed = false;
344
+ return () => {
345
+ if (removed) return;
346
+ removed = true;
347
+ const index = this.typingHandlers.indexOf(handler);
348
+ if (index !== -1) this.typingHandlers.splice(index, 1);
349
+ };
350
+ }
291
351
  // --- Receiving (async iterable) ---
292
352
  messages(options) {
293
353
  const cleanup = {};
@@ -322,6 +382,8 @@ var Bot = class _Bot {
322
382
  this.client.removeAllListeners();
323
383
  await this.client.destroy();
324
384
  this.messageHandlers.length = 0;
385
+ this.typingHandlers.length = 0;
386
+ this.typingListenerAttached = false;
325
387
  this.errorHandlers.length = 0;
326
388
  this._status = "disconnected";
327
389
  this._connectedAt = null;
@@ -391,6 +453,7 @@ var Bot = class _Bot {
391
453
  setupEventHandlers() {
392
454
  this.client.on(Events.MessageCreate, (message) => {
393
455
  try {
456
+ if (!message.author) return;
394
457
  if (message.author.id === this.client.user?.id) return;
395
458
  if (message.author.bot && !this._includeBotMessages) return;
396
459
  const event = this.toMessageEvent(message);
@@ -401,9 +464,34 @@ var Bot = class _Bot {
401
464
  this.emitError(error);
402
465
  }
403
466
  });
467
+ this.client.on("raw", (packet) => {
468
+ if (packet.t !== "MESSAGE_CREATE") return;
469
+ if (packet.d.guild_id) return;
470
+ const channelId = packet.d.channel_id;
471
+ const messageId = packet.d.id;
472
+ if (!channelId || !messageId) return;
473
+ if (this.client.channels.cache.has(channelId)) return;
474
+ void this.handleUncachedDM(channelId, messageId);
475
+ });
476
+ }
477
+ async handleUncachedDM(channelId, messageId) {
478
+ try {
479
+ const channel = await this.client.channels.fetch(channelId);
480
+ if (!channel?.isTextBased()) return;
481
+ const message = await channel.messages.fetch(messageId);
482
+ if (!message.author) return;
483
+ if (message.author.id === this.client.user?.id) return;
484
+ if (message.author.bot && !this._includeBotMessages) return;
485
+ const event = this.toMessageEvent(message);
486
+ for (const handler of this.messageHandlers) {
487
+ handler(event);
488
+ }
489
+ } catch (error) {
490
+ this.emitError(error);
491
+ }
404
492
  }
405
493
  toMessageEvent(message) {
406
- const isDM = message.channel.type === ChannelType.DM;
494
+ const isDM = message.guildId === null;
407
495
  const botUser = this.client.user;
408
496
  const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);
409
497
  let isReply = false;
@@ -434,6 +522,15 @@ var Bot = class _Bot {
434
522
  replyTo
435
523
  };
436
524
  }
525
+ toTypingEvent(typing) {
526
+ return {
527
+ channelId: typing.channel.id,
528
+ userId: typing.user.id,
529
+ startedAt: typing.startedAt,
530
+ guildId: typing.guild?.id ?? null,
531
+ raw: typing
532
+ };
533
+ }
437
534
  async sendPayloads(content, dispatcher) {
438
535
  const files = typeof content !== "string" && content.files?.length ? await validateAndBuildAttachments(content.files) : void 0;
439
536
  const payloads = toChunkedPayloads(content, files);
@@ -640,6 +737,7 @@ var ConnectorManager = class {
640
737
  botMap = /* @__PURE__ */ new Map();
641
738
  botUnsubscribers = /* @__PURE__ */ new Map();
642
739
  messageHandlers = [];
740
+ typingHandlers = [];
643
741
  lifecycleHandlers = /* @__PURE__ */ new Map();
644
742
  addBot(bot) {
645
743
  if (this.botMap.has(bot.id)) {
@@ -659,6 +757,9 @@ var ConnectorManager = class {
659
757
  this.emitLifecycle("botError", bot, error);
660
758
  })
661
759
  );
760
+ if (this.typingHandlers.length > 0) {
761
+ this.subscribeTyping(bot, unsubs);
762
+ }
662
763
  this.botUnsubscribers.set(bot.id, unsubs);
663
764
  }
664
765
  getBot(id) {
@@ -685,11 +786,35 @@ var ConnectorManager = class {
685
786
  onMessage(handler) {
686
787
  this.messageHandlers.push(handler);
687
788
  }
789
+ onTypingStart(handler) {
790
+ const isFirst = this.typingHandlers.length === 0;
791
+ this.typingHandlers.push(handler);
792
+ if (isFirst) {
793
+ for (const [id, bot] of this.botMap) {
794
+ const unsubs = this.botUnsubscribers.get(id) ?? [];
795
+ this.subscribeTyping(bot, unsubs);
796
+ }
797
+ }
798
+ }
688
799
  on(event, callback) {
689
800
  const handlers = this.lifecycleHandlers.get(event) ?? [];
690
801
  handlers.push(callback);
691
802
  this.lifecycleHandlers.set(event, handlers);
692
803
  }
804
+ subscribeTyping(bot, unsubs) {
805
+ try {
806
+ unsubs.push(
807
+ bot.onTypingStart((event) => {
808
+ for (const handler of this.typingHandlers) {
809
+ handler(event, bot);
810
+ }
811
+ })
812
+ );
813
+ } catch (error) {
814
+ const isIntentError = error instanceof Error && error.message.startsWith("onTypingStart requires");
815
+ if (!isIntentError) throw error;
816
+ }
817
+ }
693
818
  emitLifecycle(event, ...args) {
694
819
  const handlers = this.lifecycleHandlers.get(event);
695
820
  if (handlers) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bot.ts","../src/event-buffer.ts","../src/message-utils.ts","../src/attachment-utils.ts","../src/onboarding.ts","../src/connector-manager.ts"],"sourcesContent":["import { Client, ChannelType, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder, type Message } from \"discord.js\";\nimport { stat } from \"node:fs/promises\";\nimport type {\n BotConfig,\n BotStatus,\n FetchedMessage,\n FetchHistoryOptions,\n FileAttachment,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n} from \"./types.js\";\nimport { EventBuffer } from \"./event-buffer.js\";\nimport { chunkMessage } from \"./message-utils.js\";\nimport { validateAttachmentSize, type DownloadableAttachment } from \"./attachment-utils.js\";\n\nexport const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;\n\nexport const DEFAULT_INTENTS = [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n] as const;\n\nexport const DEFAULT_PARTIALS = [Partials.Channel, Partials.Message] as const;\n\ntype MessageHandler = (event: MessageEvent) => void;\ntype ErrorHandler = (error: Error) => void;\n\nexport class Bot {\n readonly id: string;\n readonly name: string;\n\n private _status: BotStatus;\n private readonly client: Client;\n private readonly sentMessages: LimitedCollection<string, SentMessage>;\n private _connectedAt: Date | null = null;\n private readonly messageHandlers: MessageHandler[] = [];\n private readonly errorHandlers: ErrorHandler[] = [];\n private readonly _includeBotMessages: boolean;\n\n private constructor(\n id: string,\n name: string,\n client: Client,\n sentMessageCacheSize: number,\n includeBotMessages: boolean,\n ) {\n this.id = id;\n this.name = name;\n this.client = client;\n this.sentMessages = new LimitedCollection({ maxSize: sentMessageCacheSize });\n this._includeBotMessages = includeBotMessages;\n this._status = \"connecting\";\n }\n\n get includeBotMessages(): boolean {\n return this._includeBotMessages;\n }\n\n get status(): BotStatus {\n return this._status;\n }\n\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n get connectedAt(): Date | null {\n return this._connectedAt;\n }\n\n /**\n * Fast path: create a bot from a complete config, connect, verify, and\n * return a ready Bot — or throw with diagnostics.\n */\n static async fromConfig(config: BotConfig): Promise<Bot> {\n const cacheSize = config.sentMessageCacheSize ?? DEFAULT_SENT_MESSAGE_CACHE_SIZE;\n if (cacheSize < 1 || !Number.isInteger(cacheSize)) {\n throw new Error(\n `sentMessageCacheSize must be a positive integer, got ${String(cacheSize)}`,\n );\n }\n\n const includeBotMessages = config.includeBotMessages ?? false;\n\n const client = new Client({\n intents: config.intents ?? [...DEFAULT_INTENTS],\n partials: config.partials ?? [...DEFAULT_PARTIALS],\n });\n\n const bot = new Bot(config.id, config.name, client, cacheSize, includeBotMessages);\n\n await bot.connect(config.token);\n bot.verify();\n bot.setupEventHandlers();\n bot._status = \"ready\";\n bot._connectedAt = new Date();\n\n return bot;\n }\n\n // --- Sending ---\n\n async send(channelId: string, content: MessageContent): Promise<SentMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload) =>\n (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload),\n );\n }\n\n async sendDM(userId: string, content: MessageContent): Promise<SentMessage[]> {\n const user = await this.client.users.fetch(userId);\n return this.sendPayloads(content, (payload) =>\n user.send(payload as Parameters<typeof user.send>[0]),\n );\n }\n\n async reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]> {\n const { channel, message } = await this.fetchMessage(channelId, messageId, \"reply to\");\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload, i) => {\n if (i === 0) {\n return message.reply(payload as Parameters<Message[\"reply\"]>[0]);\n }\n return (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload);\n });\n }\n\n // --- Channel operations ---\n\n async react(channelId: string, messageId: string, emoji: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"react to\");\n try {\n await message.react(emoji);\n } catch (error) {\n throw new Error(\n `Cannot react to message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async editMessage(channelId: string, messageId: string, content: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"edit\");\n try {\n await message.edit(content);\n } catch (error) {\n throw new Error(\n `Cannot edit message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n const clamped = Math.max(1, Math.min(limit ?? 20, 100));\n const messages = await channel.messages.fetch({ limit: clamped });\n return [...messages.values()].reverse().map((m) => this.toFetchedMessage(m));\n }\n\n async *fetchHistory(\n channelId: string,\n options: FetchHistoryOptions,\n ): AsyncGenerator<FetchedMessage[]> {\n const { after, before, limit = 500 } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS consumers\n if (after && before) {\n throw new Error('Cannot specify both \"after\" and \"before\"');\n }\n\n let cursor: string;\n let direction: \"after\" | \"before\";\n if (after) {\n cursor = after;\n direction = \"after\";\n } else if (before) {\n cursor = before;\n direction = \"before\";\n } else {\n throw new Error('At least one of \"after\" or \"before\" must be specified');\n }\n\n const effectiveLimit = Math.min(limit, 5000);\n const channel = await this.fetchTextChannel(channelId);\n\n let totalFetched = 0;\n\n while (totalFetched < effectiveLimit) {\n const pageSize = Math.min(100, effectiveLimit - totalFetched);\n\n const fetched = await this.fetchPageWithRetry(channel, {\n limit: pageSize,\n [direction]: cursor,\n });\n\n if (fetched.size === 0) break;\n\n const rawMessages = [...fetched.values()];\n const messages = rawMessages.map((m) => this.toFetchedMessage(m));\n\n yield messages;\n totalFetched += messages.length;\n\n const lastMessage = rawMessages.at(-1);\n if (!lastMessage) break;\n cursor = lastMessage.id;\n\n if (fetched.size < pageSize) break;\n }\n }\n\n async getMessageAttachments(\n channelId: string,\n messageId: string,\n ): Promise<DownloadableAttachment[]> {\n const { message } = await this.fetchMessage(channelId, messageId, \"fetch attachments for\");\n return [...message.attachments.values()].map((att) => ({\n id: att.id,\n size: att.size,\n name: att.name,\n url: att.url,\n contentType: att.contentType,\n }));\n }\n\n // --- Receiving (event-driven) ---\n\n onMessage(handler: MessageHandler): () => void {\n this.messageHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.messageHandlers.indexOf(handler);\n if (index !== -1) this.messageHandlers.splice(index, 1);\n };\n }\n\n // --- Receiving (async iterable) ---\n\n messages(options?: { filter?: MessageFilter }): AsyncIterable<MessageEvent> {\n const cleanup: { unsub?: () => void } = {};\n const buffer = new EventBuffer<MessageEvent>({\n onClose: () => cleanup.unsub?.(),\n });\n cleanup.unsub = this.onMessage((event) => {\n if (options?.filter && !options.filter(event)) return;\n buffer.push(event);\n });\n return buffer;\n }\n\n // --- Error handling ---\n\n on(_event: \"error\", handler: ErrorHandler): () => void {\n this.errorHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.errorHandlers.indexOf(handler);\n if (index !== -1) this.errorHandlers.splice(index, 1);\n };\n }\n\n private emitError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n // --- Lifecycle ---\n\n async disconnect(): Promise<void> {\n this.client.removeAllListeners();\n await this.client.destroy();\n this.messageHandlers.length = 0;\n this.errorHandlers.length = 0;\n this._status = \"disconnected\";\n this._connectedAt = null;\n }\n\n // --- Private ---\n\n private async fetchTextChannel(channelId: string) {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) {\n throw new Error(\n `Channel ${channelId} is not a text-based channel or does not exist`,\n );\n }\n return channel;\n }\n\n private async fetchMessage(\n channelId: string,\n messageId: string,\n verb: string,\n ): Promise<{ channel: Awaited<ReturnType<Bot[\"fetchTextChannel\"]>>; message: Message }> {\n const channel = await this.fetchTextChannel(channelId);\n try {\n const message = await channel.messages.fetch(messageId);\n return { channel, message };\n } catch (error) {\n throw new Error(\n `Cannot ${verb} message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private toFetchedMessage(msg: Message): FetchedMessage {\n return {\n messageId: msg.id,\n channelId: msg.channelId,\n author: {\n id: msg.author.id,\n username: msg.author.username,\n bot: msg.author.bot,\n },\n content: msg.content,\n timestamp: msg.createdAt,\n attachmentCount: msg.attachments.size,\n };\n }\n\n private async fetchPageWithRetry(\n channel: { messages: { fetch(opts: unknown): Promise<Map<string, Message>> } },\n options: Record<string, unknown>,\n maxRetryMs = 5000,\n ): Promise<Map<string, Message>> {\n try {\n return await channel.messages.fetch(options);\n } catch (err) {\n if (err instanceof Error && \"retryAfter\" in err) {\n const { retryAfter } = err;\n if (typeof retryAfter === \"number\" && retryAfter <= maxRetryMs) {\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n return channel.messages.fetch(options);\n }\n }\n throw err;\n }\n }\n\n private async connect(token: string): Promise<void> {\n this._status = \"connecting\";\n await this.client.login(token);\n }\n\n private verify(): void {\n this._status = \"verifying\";\n\n if (this.client.guilds.cache.size === 0) {\n this._status = \"failed\";\n throw new Error(\n \"Bot is not in any guild. Invite the bot to at least one server before connecting.\",\n );\n }\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.MessageCreate, (message: Message) => {\n try {\n // Always ignore own messages (prevent infinite loops)\n if (message.author.id === this.client.user?.id) return;\n // Ignore other bots unless opted in\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n }\n\n private toMessageEvent(message: Message): MessageEvent {\n const isDM = message.channel.type === ChannelType.DM;\n const botUser = this.client.user;\n const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);\n\n let isReply = false;\n let replyTo: SentMessage | null = null;\n if (message.reference?.messageId) {\n const original = this.sentMessages.get(message.reference.messageId);\n if (original) {\n isReply = true;\n replyTo = original;\n }\n }\n\n return {\n messageId: message.id,\n author: {\n id: message.author.id,\n username: message.author.username,\n bot: message.author.bot,\n },\n content: message.content,\n channelId: message.channelId,\n guildId: message.guildId ?? null,\n timestamp: message.createdAt,\n mentions: [...message.mentions.users.keys()],\n raw: message,\n isDM,\n isMention,\n isReply,\n replyTo,\n };\n }\n\n private async sendPayloads(\n content: MessageContent,\n dispatcher: (payload: ChunkedPayload, index: number) => Promise<Message>,\n ): Promise<SentMessage[]> {\n const files =\n typeof content !== \"string\" && content.files?.length\n ? await validateAndBuildAttachments(content.files)\n : undefined;\n const payloads = toChunkedPayloads(content, files);\n const results: SentMessage[] = [];\n let i = 0;\n for (const payload of payloads) {\n const msg = await dispatcher(payload, i++);\n results.push(this.trackSentMessage(msg));\n }\n return results;\n }\n\n private trackSentMessage(message: Message): SentMessage {\n const sent: SentMessage = {\n messageId: message.id,\n channelId: message.channelId,\n timestamp: message.createdAt,\n raw: message,\n };\n this.sentMessages.set(sent.messageId, sent);\n return sent;\n }\n}\n\ninterface ChunkedPayload {\n content?: string;\n embeds?: unknown[];\n files?: AttachmentBuilder[];\n}\n\nfunction toChunkedPayloads(\n content: MessageContent,\n files?: AttachmentBuilder[],\n): ChunkedPayload[] {\n if (typeof content === \"string\") {\n const chunks = chunkMessage(content);\n return chunks.map((text, i) => ({\n content: text,\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n }\n const text = content.content ?? \"\";\n const chunks = chunkMessage(text);\n if (chunks.length === 0) {\n return [{ embeds: content.embeds, ...(files?.length ? { files } : {}) }];\n }\n return chunks.map((chunk, i) => ({\n content: chunk,\n ...(i === 0 && content.embeds ? { embeds: content.embeds } : {}),\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n}\n\nconst MAX_FILES_PER_MESSAGE = 10;\n\nasync function validateAndBuildAttachments(\n files: FileAttachment[],\n): Promise<AttachmentBuilder[]> {\n if (files.length > MAX_FILES_PER_MESSAGE) {\n throw new Error(\n `Too many attachments: ${String(files.length)} exceeds Discord's limit of ${String(MAX_FILES_PER_MESSAGE)} attachments per message`,\n );\n }\n\n const sizes = await Promise.all(\n files.map((file) =>\n typeof file.data === \"string\"\n ? stat(file.data).then((st) => st.size)\n : Promise.resolve(file.data.byteLength),\n ),\n );\n\n return files.map((file, i) => {\n const size = sizes[i];\n if (size === undefined) throw new Error(`Missing size for attachment: ${file.name}`);\n validateAttachmentSize({ size, name: file.name, id: file.name });\n return new AttachmentBuilder(file.data, { name: file.name });\n });\n}\n","/**\n * Single-consumer async iterable buffer. Each call to messages() on a Bot\n * creates its own buffer. Do not share a single EventBuffer between\n * multiple for-await consumers.\n */\nexport class EventBuffer<T> implements AsyncIterable<T> {\n private buffer: T[] = [];\n private resolve: ((value: IteratorResult<T>) => void) | null = null;\n private closed = false;\n private readonly onClose: (() => void) | undefined;\n\n constructor(options?: { onClose?: () => void }) {\n this.onClose = options?.onClose;\n }\n\n push(value: T): void {\n if (this.closed) return;\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value, done: false });\n } else {\n this.buffer.push(value);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.onClose?.();\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value: undefined as unknown as T, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.buffer.length > 0) {\n // Length check guarantees shift() returns a value\n const value = this.buffer.shift() as T;\n return Promise.resolve({ value, done: false });\n }\n if (this.closed) {\n return Promise.resolve({\n value: undefined as unknown as T,\n done: true,\n });\n }\n return new Promise((resolve) => {\n this.resolve = resolve;\n });\n },\n return: (): Promise<IteratorResult<T>> => {\n this.close();\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n },\n };\n }\n}\n","// Message utility functions for discord-bot-lib\n\n/** Discord's hard limit on message length. */\nexport const DISCORD_MAX_MESSAGE_LENGTH = 2000;\n\n/**\n * Splits text into chunks that fit within Discord's message length limit.\n * Prefers splitting at paragraph boundaries (\\n\\n), then line breaks (\\n),\n * then spaces, then hard-cuts as a last resort.\n *\n * @param text - The text to split\n * @param limit - Maximum characters per chunk (default: {@link DISCORD_MAX_MESSAGE_LENGTH})\n * @returns Array of text chunks, each within the limit\n */\nexport function chunkMessage(\n text: string,\n limit: number = DISCORD_MAX_MESSAGE_LENGTH,\n): string[] {\n if (!text) return [];\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const halfLimit = limit / 2;\n let rest = text;\n\n while (rest.length > limit) {\n const para = rest.lastIndexOf(\"\\n\\n\", limit);\n const line = rest.lastIndexOf(\"\\n\", limit);\n const space = rest.lastIndexOf(\" \", limit);\n const cut =\n para > halfLimit\n ? para\n : line > halfLimit\n ? line\n : space > 0\n ? space\n : limit;\n\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).replace(/^\\n+/, \"\");\n }\n\n if (rest) chunks.push(rest);\n\n return chunks;\n}\n","// Attachment utility functions for discord-bot-lib\n\n/** Maximum attachment size in bytes (25 MB). Matches Discord's default file size limit. */\nexport const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024; // 26_214_400\n\n/** Shape compatible with discord.js Attachment for attachment utilities. */\nexport interface AttachmentLike {\n size: number;\n name?: string | null;\n id: string;\n}\n\n/**\n * Validates that an attachment does not exceed Discord's size limit.\n * Designed for use in both upload (sending files) and download paths.\n *\n * @throws Error if the attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n */\nexport function validateAttachmentSize(attachment: AttachmentLike): void {\n if (attachment.size > MAX_ATTACHMENT_BYTES) {\n const actualMB = (attachment.size / 1024 / 1024).toFixed(1);\n const limitMB = MAX_ATTACHMENT_BYTES / 1024 / 1024;\n const nameInfo = attachment.name ? `: ${attachment.name}` : \"\";\n throw new Error(\n `Attachment too large (${actualMB} MB > ${String(limitMB)} MB limit)${nameInfo}`,\n );\n }\n}\n\n/**\n * Sanitizes an attachment filename by replacing unsafe characters\n * (`[`, `]`, `\\r`, `\\n`, `;`) with underscores. Falls back to the\n * attachment ID when the name is missing or empty after sanitization.\n */\nconst UNSAFE_CHARS = /[[\\]\\r\\n;]/g;\n\nexport function sanitizeAttachmentName(\n attachment: Pick<AttachmentLike, \"name\" | \"id\">,\n): string {\n const raw = attachment.name;\n if (!raw || raw.replace(UNSAFE_CHARS, \"\").length === 0) {\n return attachment.id;\n }\n return raw.replace(UNSAFE_CHARS, \"_\");\n}\n\n/** Result of downloading a Discord attachment. */\nexport interface DownloadedAttachment {\n buffer: Buffer;\n filename: string;\n contentType: string;\n}\n\n/** Shape accepted by {@link downloadAttachment}. Compatible with discord.js `Attachment`. */\nexport interface DownloadableAttachment extends AttachmentLike {\n url: string;\n contentType?: string | null;\n}\n\n/**\n * Downloads a Discord message attachment to a raw buffer.\n * Validates size and sanitizes filename before returning.\n *\n * @throws Error if attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n * @throws Error if the fetch response is not ok\n */\nexport async function downloadAttachment(\n attachment: DownloadableAttachment,\n): Promise<DownloadedAttachment> {\n validateAttachmentSize(attachment);\n\n const res = await fetch(attachment.url);\n if (!res.ok) {\n throw new Error(\n `Failed to download attachment: ${String(res.status)} ${res.statusText}`,\n );\n }\n\n const ab = await res.arrayBuffer();\n const buffer = Buffer.from(ab, 0, ab.byteLength);\n const filename = sanitizeAttachmentName(attachment);\n const contentType = attachment.contentType ?? \"application/octet-stream\";\n\n return { buffer, filename, contentType };\n}\n","import { Client } from \"discord.js\";\nimport type { OnboardingStep, OnboardingStepStatus, StepResult } from \"./types.js\";\nimport { Bot, DEFAULT_INTENTS, DEFAULT_PARTIALS } from \"./bot.js\";\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\ntype StepAction = (...args: unknown[]) => Promise<StepResult>;\n\nclass OnboardingStepImpl implements OnboardingStep {\n id: string;\n label: string;\n instructions: string;\n status: OnboardingStepStatus;\n error?: string;\n nextSibling?: OnboardingStepImpl;\n\n private readonly _action: StepAction;\n private readonly _dependencies: OnboardingStepImpl[];\n\n constructor(\n id: string,\n label: string,\n instructions: string,\n action: StepAction,\n dependencies: OnboardingStepImpl[] = [],\n initialStatus: OnboardingStepStatus = \"pending\",\n ) {\n this.id = id;\n this.label = label;\n this.instructions = instructions;\n this._action = action;\n this._dependencies = dependencies;\n this.status = initialStatus;\n }\n\n async complete(...args: unknown[]): Promise<StepResult> {\n // Check dependencies\n for (const dep of this._dependencies) {\n if (dep.status !== \"completed\") {\n const error = `Cannot complete '${this.id}': dependency '${dep.id}' is not completed.`;\n this.status = \"failed\";\n this.error = error;\n return { success: false, error };\n }\n }\n\n try {\n const result = await this._action(...args);\n if (result.success) {\n this.status = \"completed\";\n this.error = undefined;\n // Promote next step to ready\n if (this.nextSibling?.status === \"pending\") {\n this.nextSibling.status = \"ready\";\n result.nextStep = this.nextSibling;\n }\n } else {\n this.status = \"failed\";\n this.error = result.error;\n }\n return result;\n } catch (err) {\n const message = toErrorMessage(err);\n this.status = \"failed\";\n this.error = message;\n return { success: false, error: message };\n }\n }\n}\n\nexport class BotOnboarding {\n readonly id: string;\n readonly name: string;\n readonly steps: OnboardingStep[];\n\n private _bot: Bot | undefined;\n private _token: string | undefined;\n\n constructor(id: string, name: string) {\n this.id = id;\n this.name = name;\n const provideToken = new OnboardingStepImpl(\n \"provide_token\",\n \"Provide Bot Token\",\n \"Enter your Discord bot token. You can find this in the Discord Developer Portal under your application's Bot settings.\",\n async (...args: unknown[]) => {\n const token = args[0] as string;\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(token);\n await client.destroy();\n this._token = token;\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [],\n \"ready\",\n );\n\n const inviteToServer = new OnboardingStepImpl(\n \"invite_to_server\",\n \"Invite Bot to Server\",\n \"Add the bot to at least one Discord server using the OAuth2 URL from the Developer Portal.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(this._token);\n if (client.guilds.cache.size === 0) {\n await client.destroy();\n return { success: false, error: \"Bot is not in any server yet\" };\n }\n await client.destroy();\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [provideToken],\n );\n\n const verifyPermissions = new OnboardingStepImpl(\n \"verify_permissions\",\n \"Verify Permissions\",\n \"Verify the bot has the required permissions in your server.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n try {\n this._bot = await Bot.fromConfig({\n id: this.id,\n name: this.name,\n token: this._token,\n });\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [inviteToServer],\n );\n\n // Wire nextSibling chain for step promotion\n provideToken.nextSibling = inviteToServer;\n inviteToServer.nextSibling = verifyPermissions;\n\n this.steps = [provideToken, inviteToServer, verifyPermissions];\n }\n\n get bot(): Bot | undefined {\n return this._bot;\n }\n}\n","import { Bot } from \"./bot.js\";\nimport { BotOnboarding } from \"./onboarding.js\";\nimport type { MessageEvent } from \"./types.js\";\n\ntype LifecycleEvent = \"botReady\" | \"botDisconnected\" | \"botError\";\n\nexport class ConnectorManager {\n private botMap = new Map<string, Bot>();\n private botUnsubscribers = new Map<string, (() => void)[]>();\n private messageHandlers: ((event: MessageEvent, bot: Bot) => void)[] = [];\n private lifecycleHandlers = new Map<LifecycleEvent, ((...args: unknown[]) => void)[]>();\n\n addBot(bot: Bot): void {\n if (this.botMap.has(bot.id)) {\n throw new Error(`Bot with id \"${bot.id}\" already exists in this manager.`);\n }\n this.botMap.set(bot.id, bot);\n\n const unsubs: (() => void)[] = [];\n unsubs.push(\n bot.onMessage((event) => {\n for (const handler of this.messageHandlers) {\n handler(event, bot);\n }\n }),\n );\n unsubs.push(\n bot.on(\"error\", (error) => {\n this.emitLifecycle(\"botError\", bot, error);\n }),\n );\n this.botUnsubscribers.set(bot.id, unsubs);\n }\n\n getBot(id: string): Bot | undefined {\n return this.botMap.get(id);\n }\n\n get bots(): Iterable<Bot> {\n return this.botMap.values();\n }\n\n removeBot(id: string): void {\n const unsubs = this.botUnsubscribers.get(id);\n if (unsubs) {\n for (const unsub of unsubs) unsub();\n this.botUnsubscribers.delete(id);\n }\n this.botMap.delete(id);\n }\n\n status(): { id: string; name: string; status: string }[] {\n return [...this.botMap.values()].map((bot) => ({\n id: bot.id,\n name: bot.name,\n status: bot.status,\n }));\n }\n\n onMessage(handler: (event: MessageEvent, bot: Bot) => void): void {\n this.messageHandlers.push(handler);\n }\n\n on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void {\n const handlers = this.lifecycleHandlers.get(event) ?? [];\n handlers.push(callback);\n this.lifecycleHandlers.set(event, handlers);\n }\n\n private emitLifecycle(event: LifecycleEvent, ...args: unknown[]): void {\n const handlers = this.lifecycleHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n handler(...args);\n }\n }\n }\n\n onboardBot(id: string, name: string): BotOnboarding {\n return new BotOnboarding(id, name);\n }\n\n async disconnectAll(): Promise<void> {\n await Promise.all([...this.botMap.values()].map((bot) => bot.disconnect()));\n }\n\n async shutdown(): Promise<void> {\n await this.disconnectAll();\n }\n}\n"],"mappings":"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","names":["chunks","text","Client","Client"]}
1
+ {"version":3,"sources":["../src/bot.ts","../src/event-buffer.ts","../src/message-utils.ts","../src/attachment-utils.ts","../src/onboarding.ts","../src/connector-manager.ts"],"sourcesContent":["import { Client, GatewayIntentBits, Partials, Events, LimitedCollection, AttachmentBuilder, type Message, type Typing } from \"discord.js\";\nimport { stat } from \"node:fs/promises\";\nimport type {\n BotConfig,\n BotStatus,\n FetchedMessage,\n FetchHistoryOptions,\n FileAttachment,\n MessageEvent,\n SentMessage,\n MessageContent,\n MessageFilter,\n TypingEvent,\n} from \"./types.js\";\nimport { EventBuffer } from \"./event-buffer.js\";\nimport { chunkMessage } from \"./message-utils.js\";\nimport { validateAttachmentSize, type DownloadableAttachment } from \"./attachment-utils.js\";\n\nexport const DEFAULT_SENT_MESSAGE_CACHE_SIZE = 1000;\n\nexport const DEFAULT_INTENTS = [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n] as const;\n\nexport const DEFAULT_PARTIALS = [Partials.Channel, Partials.Message] as const;\n\ntype MessageHandler = (event: MessageEvent) => void;\ntype TypingHandler = (event: TypingEvent) => void;\ntype ErrorHandler = (error: Error) => void;\n\nexport class Bot {\n readonly id: string;\n readonly name: string;\n\n private _status: BotStatus;\n private readonly client: Client;\n private readonly sentMessages: LimitedCollection<string, SentMessage>;\n private _connectedAt: Date | null = null;\n private readonly messageHandlers: MessageHandler[] = [];\n private readonly typingHandlers: TypingHandler[] = [];\n private readonly errorHandlers: ErrorHandler[] = [];\n private readonly _includeBotMessages: boolean;\n private readonly resolvedIntents: readonly GatewayIntentBits[];\n private typingListenerAttached = false;\n\n private constructor(\n id: string,\n name: string,\n client: Client,\n sentMessageCacheSize: number,\n includeBotMessages: boolean,\n resolvedIntents: readonly GatewayIntentBits[],\n ) {\n this.id = id;\n this.name = name;\n this.client = client;\n this.sentMessages = new LimitedCollection({ maxSize: sentMessageCacheSize });\n this._includeBotMessages = includeBotMessages;\n this.resolvedIntents = resolvedIntents;\n this._status = \"connecting\";\n }\n\n get includeBotMessages(): boolean {\n return this._includeBotMessages;\n }\n\n get status(): BotStatus {\n return this._status;\n }\n\n get guildCount(): number {\n return this.client.guilds.cache.size;\n }\n\n get connectedAt(): Date | null {\n return this._connectedAt;\n }\n\n /**\n * Fast path: create a bot from a complete config, connect, verify, and\n * return a ready Bot — or throw with diagnostics.\n */\n static async fromConfig(config: BotConfig): Promise<Bot> {\n const cacheSize = config.sentMessageCacheSize ?? DEFAULT_SENT_MESSAGE_CACHE_SIZE;\n if (cacheSize < 1 || !Number.isInteger(cacheSize)) {\n throw new Error(\n `sentMessageCacheSize must be a positive integer, got ${String(cacheSize)}`,\n );\n }\n\n const includeBotMessages = config.includeBotMessages ?? false;\n const intents = config.intents ?? [...DEFAULT_INTENTS];\n\n const client = new Client({\n intents,\n partials: config.partials ?? [...DEFAULT_PARTIALS],\n });\n\n const bot = new Bot(config.id, config.name, client, cacheSize, includeBotMessages, intents);\n\n await bot.connect(config.token);\n bot.verify();\n bot.setupEventHandlers();\n bot._status = \"ready\";\n bot._connectedAt = new Date();\n\n return bot;\n }\n\n // --- Sending ---\n\n async send(channelId: string, content: MessageContent): Promise<SentMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload) =>\n (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload),\n );\n }\n\n async sendDM(userId: string, content: MessageContent): Promise<SentMessage[]> {\n const user = await this.client.users.fetch(userId);\n return this.sendPayloads(content, (payload) =>\n user.send(payload as Parameters<typeof user.send>[0]),\n );\n }\n\n async reply(channelId: string, messageId: string, content: MessageContent): Promise<SentMessage[]> {\n const { channel, message } = await this.fetchMessage(channelId, messageId, \"reply to\");\n if (!(\"send\" in channel)) {\n throw new Error(`Channel ${channelId} is not a sendable channel`);\n }\n return this.sendPayloads(content, (payload, i) => {\n if (i === 0) {\n return message.reply(payload as Parameters<Message[\"reply\"]>[0]);\n }\n return (channel as unknown as { send(opts: unknown): Promise<Message> }).send(payload);\n });\n }\n\n // --- Channel operations ---\n\n async react(channelId: string, messageId: string, emoji: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"react to\");\n try {\n await message.react(emoji);\n } catch (error) {\n throw new Error(\n `Cannot react to message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async editMessage(channelId: string, messageId: string, content: string): Promise<void> {\n const { message } = await this.fetchMessage(channelId, messageId, \"edit\");\n try {\n await message.edit(content);\n } catch (error) {\n throw new Error(\n `Cannot edit message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n const channel = await this.fetchTextChannel(channelId);\n const typingChannel = channel as unknown as { sendTyping?: () => Promise<void> };\n if (typeof typingChannel.sendTyping !== \"function\") {\n throw new Error(`Channel ${channelId} does not support typing indicators`);\n }\n try {\n await typingChannel.sendTyping();\n } catch (error) {\n throw new Error(\n `Cannot send typing to channel ${channelId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async sendDMTyping(userId: string): Promise<void> {\n try {\n const user = await this.client.users.fetch(userId);\n const dm = await user.createDM();\n await dm.sendTyping();\n } catch (error) {\n throw new Error(\n `Cannot send typing to user ${userId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n async fetchMessages(channelId: string, limit?: number): Promise<FetchedMessage[]> {\n const channel = await this.fetchTextChannel(channelId);\n const clamped = Math.max(1, Math.min(limit ?? 20, 100));\n const messages = await channel.messages.fetch({ limit: clamped });\n return [...messages.values()].reverse().map((m) => this.toFetchedMessage(m));\n }\n\n async *fetchHistory(\n channelId: string,\n options: FetchHistoryOptions,\n ): AsyncGenerator<FetchedMessage[]> {\n const { after, before, limit = 500 } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS consumers\n if (after && before) {\n throw new Error('Cannot specify both \"after\" and \"before\"');\n }\n\n let cursor: string;\n let direction: \"after\" | \"before\";\n if (after) {\n cursor = after;\n direction = \"after\";\n } else if (before) {\n cursor = before;\n direction = \"before\";\n } else {\n throw new Error('At least one of \"after\" or \"before\" must be specified');\n }\n\n const effectiveLimit = Math.min(limit, 5000);\n const channel = await this.fetchTextChannel(channelId);\n\n let totalFetched = 0;\n\n while (totalFetched < effectiveLimit) {\n const pageSize = Math.min(100, effectiveLimit - totalFetched);\n\n const fetched = await this.fetchPageWithRetry(channel, {\n limit: pageSize,\n [direction]: cursor,\n });\n\n if (fetched.size === 0) break;\n\n const rawMessages = [...fetched.values()];\n const messages = rawMessages.map((m) => this.toFetchedMessage(m));\n\n yield messages;\n totalFetched += messages.length;\n\n const lastMessage = rawMessages.at(-1);\n if (!lastMessage) break;\n cursor = lastMessage.id;\n\n if (fetched.size < pageSize) break;\n }\n }\n\n async getMessageAttachments(\n channelId: string,\n messageId: string,\n ): Promise<DownloadableAttachment[]> {\n const { message } = await this.fetchMessage(channelId, messageId, \"fetch attachments for\");\n return [...message.attachments.values()].map((att) => ({\n id: att.id,\n size: att.size,\n name: att.name,\n url: att.url,\n contentType: att.contentType,\n }));\n }\n\n // --- Receiving (event-driven) ---\n\n onMessage(handler: MessageHandler): () => void {\n this.messageHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.messageHandlers.indexOf(handler);\n if (index !== -1) this.messageHandlers.splice(index, 1);\n };\n }\n\n onTypingStart(handler: TypingHandler): () => void {\n const hasGuildTyping = this.resolvedIntents.includes(GatewayIntentBits.GuildMessageTyping);\n const hasDMTyping = this.resolvedIntents.includes(GatewayIntentBits.DirectMessageTyping);\n if (!hasGuildTyping && !hasDMTyping) {\n throw new Error(\n \"onTypingStart requires GuildMessageTyping and/or DirectMessageTyping intents. \" +\n \"Add them to the intents array in BotConfig.\",\n );\n }\n\n if (!this.typingListenerAttached) {\n this.typingListenerAttached = true;\n this.client.on(Events.TypingStart, (typing: Typing) => {\n try {\n const event = this.toTypingEvent(typing);\n for (const h of this.typingHandlers) {\n h(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n }\n\n this.typingHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.typingHandlers.indexOf(handler);\n if (index !== -1) this.typingHandlers.splice(index, 1);\n };\n }\n\n // --- Receiving (async iterable) ---\n\n messages(options?: { filter?: MessageFilter }): AsyncIterable<MessageEvent> {\n const cleanup: { unsub?: () => void } = {};\n const buffer = new EventBuffer<MessageEvent>({\n onClose: () => cleanup.unsub?.(),\n });\n cleanup.unsub = this.onMessage((event) => {\n if (options?.filter && !options.filter(event)) return;\n buffer.push(event);\n });\n return buffer;\n }\n\n // --- Error handling ---\n\n on(_event: \"error\", handler: ErrorHandler): () => void {\n this.errorHandlers.push(handler);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n const index = this.errorHandlers.indexOf(handler);\n if (index !== -1) this.errorHandlers.splice(index, 1);\n };\n }\n\n private emitError(error: unknown): void {\n const err = error instanceof Error ? error : new Error(String(error));\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n // --- Lifecycle ---\n\n async disconnect(): Promise<void> {\n this.client.removeAllListeners();\n await this.client.destroy();\n this.messageHandlers.length = 0;\n this.typingHandlers.length = 0;\n this.typingListenerAttached = false;\n this.errorHandlers.length = 0;\n this._status = \"disconnected\";\n this._connectedAt = null;\n }\n\n // --- Private ---\n\n private async fetchTextChannel(channelId: string) {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) {\n throw new Error(\n `Channel ${channelId} is not a text-based channel or does not exist`,\n );\n }\n return channel;\n }\n\n private async fetchMessage(\n channelId: string,\n messageId: string,\n verb: string,\n ): Promise<{ channel: Awaited<ReturnType<Bot[\"fetchTextChannel\"]>>; message: Message }> {\n const channel = await this.fetchTextChannel(channelId);\n try {\n const message = await channel.messages.fetch(messageId);\n return { channel, message };\n } catch (error) {\n throw new Error(\n `Cannot ${verb} message ${messageId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private toFetchedMessage(msg: Message): FetchedMessage {\n return {\n messageId: msg.id,\n channelId: msg.channelId,\n author: {\n id: msg.author.id,\n username: msg.author.username,\n bot: msg.author.bot,\n },\n content: msg.content,\n timestamp: msg.createdAt,\n attachmentCount: msg.attachments.size,\n };\n }\n\n private async fetchPageWithRetry(\n channel: { messages: { fetch(opts: unknown): Promise<Map<string, Message>> } },\n options: Record<string, unknown>,\n maxRetryMs = 5000,\n ): Promise<Map<string, Message>> {\n try {\n return await channel.messages.fetch(options);\n } catch (err) {\n if (err instanceof Error && \"retryAfter\" in err) {\n const { retryAfter } = err;\n if (typeof retryAfter === \"number\" && retryAfter <= maxRetryMs) {\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n return channel.messages.fetch(options);\n }\n }\n throw err;\n }\n }\n\n private async connect(token: string): Promise<void> {\n this._status = \"connecting\";\n await this.client.login(token);\n }\n\n private verify(): void {\n this._status = \"verifying\";\n\n if (this.client.guilds.cache.size === 0) {\n this._status = \"failed\";\n throw new Error(\n \"Bot is not in any guild. Invite the bot to at least one server before connecting.\",\n );\n }\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.MessageCreate, (message: Message) => {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null with Partials.Message\n if (!message.author) return;\n // Always ignore own messages (prevent infinite loops)\n if (message.author.id === this.client.user?.id) return;\n // Ignore other bots unless opted in\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n });\n\n // Workaround: discord.js v14 silently drops messageCreate for DM channels\n // not in cache — MessageCreateAction.getChannel() fails to construct a\n // DMChannel when the raw payload lacks a `type` field. Catch these here.\n this.client.on(\"raw\" as string, (packet: { t: string; d: { id?: string; channel_id?: string; guild_id?: string } }) => {\n if (packet.t !== \"MESSAGE_CREATE\") return;\n if (packet.d.guild_id) return;\n const channelId = packet.d.channel_id;\n const messageId = packet.d.id;\n if (!channelId || !messageId) return;\n if (this.client.channels.cache.has(channelId)) return;\n\n void this.handleUncachedDM(channelId, messageId);\n });\n }\n\n private async handleUncachedDM(channelId: string, messageId: string): Promise<void> {\n try {\n const channel = await this.client.channels.fetch(channelId);\n if (!channel?.isTextBased()) return;\n\n const message = await channel.messages.fetch(messageId);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null with Partials.Message\n if (!message.author) return;\n if (message.author.id === this.client.user?.id) return;\n if (message.author.bot && !this._includeBotMessages) return;\n\n const event = this.toMessageEvent(message);\n for (const handler of this.messageHandlers) {\n handler(event);\n }\n } catch (error) {\n this.emitError(error);\n }\n }\n\n private toMessageEvent(message: Message): MessageEvent {\n const isDM = message.guildId === null;\n const botUser = this.client.user;\n const isMention = isDM || (botUser ? message.mentions.has(botUser.id) : false);\n\n let isReply = false;\n let replyTo: SentMessage | null = null;\n if (message.reference?.messageId) {\n const original = this.sentMessages.get(message.reference.messageId);\n if (original) {\n isReply = true;\n replyTo = original;\n }\n }\n\n return {\n messageId: message.id,\n author: {\n id: message.author.id,\n username: message.author.username,\n bot: message.author.bot,\n },\n content: message.content,\n channelId: message.channelId,\n guildId: message.guildId ?? null,\n timestamp: message.createdAt,\n mentions: [...message.mentions.users.keys()],\n raw: message,\n isDM,\n isMention,\n isReply,\n replyTo,\n };\n }\n\n private toTypingEvent(typing: Typing): TypingEvent {\n return {\n channelId: typing.channel.id,\n userId: typing.user.id,\n startedAt: typing.startedAt,\n guildId: typing.guild?.id ?? null,\n raw: typing,\n };\n }\n\n private async sendPayloads(\n content: MessageContent,\n dispatcher: (payload: ChunkedPayload, index: number) => Promise<Message>,\n ): Promise<SentMessage[]> {\n const files =\n typeof content !== \"string\" && content.files?.length\n ? await validateAndBuildAttachments(content.files)\n : undefined;\n const payloads = toChunkedPayloads(content, files);\n const results: SentMessage[] = [];\n let i = 0;\n for (const payload of payloads) {\n const msg = await dispatcher(payload, i++);\n results.push(this.trackSentMessage(msg));\n }\n return results;\n }\n\n private trackSentMessage(message: Message): SentMessage {\n const sent: SentMessage = {\n messageId: message.id,\n channelId: message.channelId,\n timestamp: message.createdAt,\n raw: message,\n };\n this.sentMessages.set(sent.messageId, sent);\n return sent;\n }\n}\n\ninterface ChunkedPayload {\n content?: string;\n embeds?: unknown[];\n files?: AttachmentBuilder[];\n}\n\nfunction toChunkedPayloads(\n content: MessageContent,\n files?: AttachmentBuilder[],\n): ChunkedPayload[] {\n if (typeof content === \"string\") {\n const chunks = chunkMessage(content);\n return chunks.map((text, i) => ({\n content: text,\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n }\n const text = content.content ?? \"\";\n const chunks = chunkMessage(text);\n if (chunks.length === 0) {\n return [{ embeds: content.embeds, ...(files?.length ? { files } : {}) }];\n }\n return chunks.map((chunk, i) => ({\n content: chunk,\n ...(i === 0 && content.embeds ? { embeds: content.embeds } : {}),\n ...(i === 0 && files?.length ? { files } : {}),\n }));\n}\n\nconst MAX_FILES_PER_MESSAGE = 10;\n\nasync function validateAndBuildAttachments(\n files: FileAttachment[],\n): Promise<AttachmentBuilder[]> {\n if (files.length > MAX_FILES_PER_MESSAGE) {\n throw new Error(\n `Too many attachments: ${String(files.length)} exceeds Discord's limit of ${String(MAX_FILES_PER_MESSAGE)} attachments per message`,\n );\n }\n\n const sizes = await Promise.all(\n files.map((file) =>\n typeof file.data === \"string\"\n ? stat(file.data).then((st) => st.size)\n : Promise.resolve(file.data.byteLength),\n ),\n );\n\n return files.map((file, i) => {\n const size = sizes[i];\n if (size === undefined) throw new Error(`Missing size for attachment: ${file.name}`);\n validateAttachmentSize({ size, name: file.name, id: file.name });\n return new AttachmentBuilder(file.data, { name: file.name });\n });\n}\n","/**\n * Single-consumer async iterable buffer. Each call to messages() on a Bot\n * creates its own buffer. Do not share a single EventBuffer between\n * multiple for-await consumers.\n */\nexport class EventBuffer<T> implements AsyncIterable<T> {\n private buffer: T[] = [];\n private resolve: ((value: IteratorResult<T>) => void) | null = null;\n private closed = false;\n private readonly onClose: (() => void) | undefined;\n\n constructor(options?: { onClose?: () => void }) {\n this.onClose = options?.onClose;\n }\n\n push(value: T): void {\n if (this.closed) return;\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value, done: false });\n } else {\n this.buffer.push(value);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.onClose?.();\n if (this.resolve) {\n const r = this.resolve;\n this.resolve = null;\n r({ value: undefined as unknown as T, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.buffer.length > 0) {\n // Length check guarantees shift() returns a value\n const value = this.buffer.shift() as T;\n return Promise.resolve({ value, done: false });\n }\n if (this.closed) {\n return Promise.resolve({\n value: undefined as unknown as T,\n done: true,\n });\n }\n return new Promise((resolve) => {\n this.resolve = resolve;\n });\n },\n return: (): Promise<IteratorResult<T>> => {\n this.close();\n return Promise.resolve({ value: undefined as unknown as T, done: true });\n },\n };\n }\n}\n","// Message utility functions for discord-bot-lib\n\n/** Discord's hard limit on message length. */\nexport const DISCORD_MAX_MESSAGE_LENGTH = 2000;\n\n/**\n * Splits text into chunks that fit within Discord's message length limit.\n * Prefers splitting at paragraph boundaries (\\n\\n), then line breaks (\\n),\n * then spaces, then hard-cuts as a last resort.\n *\n * @param text - The text to split\n * @param limit - Maximum characters per chunk (default: {@link DISCORD_MAX_MESSAGE_LENGTH})\n * @returns Array of text chunks, each within the limit\n */\nexport function chunkMessage(\n text: string,\n limit: number = DISCORD_MAX_MESSAGE_LENGTH,\n): string[] {\n if (!text) return [];\n if (text.length <= limit) return [text];\n\n const chunks: string[] = [];\n const halfLimit = limit / 2;\n let rest = text;\n\n while (rest.length > limit) {\n const para = rest.lastIndexOf(\"\\n\\n\", limit);\n const line = rest.lastIndexOf(\"\\n\", limit);\n const space = rest.lastIndexOf(\" \", limit);\n const cut =\n para > halfLimit\n ? para\n : line > halfLimit\n ? line\n : space > 0\n ? space\n : limit;\n\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).replace(/^\\n+/, \"\");\n }\n\n if (rest) chunks.push(rest);\n\n return chunks;\n}\n","// Attachment utility functions for discord-bot-lib\n\n/** Maximum attachment size in bytes (25 MB). Matches Discord's default file size limit. */\nexport const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024; // 26_214_400\n\n/** Shape compatible with discord.js Attachment for attachment utilities. */\nexport interface AttachmentLike {\n size: number;\n name?: string | null;\n id: string;\n}\n\n/**\n * Validates that an attachment does not exceed Discord's size limit.\n * Designed for use in both upload (sending files) and download paths.\n *\n * @throws Error if the attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n */\nexport function validateAttachmentSize(attachment: AttachmentLike): void {\n if (attachment.size > MAX_ATTACHMENT_BYTES) {\n const actualMB = (attachment.size / 1024 / 1024).toFixed(1);\n const limitMB = MAX_ATTACHMENT_BYTES / 1024 / 1024;\n const nameInfo = attachment.name ? `: ${attachment.name}` : \"\";\n throw new Error(\n `Attachment too large (${actualMB} MB > ${String(limitMB)} MB limit)${nameInfo}`,\n );\n }\n}\n\n/**\n * Sanitizes an attachment filename by replacing unsafe characters\n * (`[`, `]`, `\\r`, `\\n`, `;`) with underscores. Falls back to the\n * attachment ID when the name is missing or empty after sanitization.\n */\nconst UNSAFE_CHARS = /[[\\]\\r\\n;]/g;\n\nexport function sanitizeAttachmentName(\n attachment: Pick<AttachmentLike, \"name\" | \"id\">,\n): string {\n const raw = attachment.name;\n if (!raw || raw.replace(UNSAFE_CHARS, \"\").length === 0) {\n return attachment.id;\n }\n return raw.replace(UNSAFE_CHARS, \"_\");\n}\n\n/** Result of downloading a Discord attachment. */\nexport interface DownloadedAttachment {\n buffer: Buffer;\n filename: string;\n contentType: string;\n}\n\n/** Shape accepted by {@link downloadAttachment}. Compatible with discord.js `Attachment`. */\nexport interface DownloadableAttachment extends AttachmentLike {\n url: string;\n contentType?: string | null;\n}\n\n/**\n * Downloads a Discord message attachment to a raw buffer.\n * Validates size and sanitizes filename before returning.\n *\n * @throws Error if attachment exceeds {@link MAX_ATTACHMENT_BYTES}\n * @throws Error if the fetch response is not ok\n */\nexport async function downloadAttachment(\n attachment: DownloadableAttachment,\n): Promise<DownloadedAttachment> {\n validateAttachmentSize(attachment);\n\n const res = await fetch(attachment.url);\n if (!res.ok) {\n throw new Error(\n `Failed to download attachment: ${String(res.status)} ${res.statusText}`,\n );\n }\n\n const ab = await res.arrayBuffer();\n const buffer = Buffer.from(ab, 0, ab.byteLength);\n const filename = sanitizeAttachmentName(attachment);\n const contentType = attachment.contentType ?? \"application/octet-stream\";\n\n return { buffer, filename, contentType };\n}\n","import { Client } from \"discord.js\";\nimport type { OnboardingStep, OnboardingStepStatus, StepResult } from \"./types.js\";\nimport { Bot, DEFAULT_INTENTS, DEFAULT_PARTIALS } from \"./bot.js\";\n\nfunction toErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\ntype StepAction = (...args: unknown[]) => Promise<StepResult>;\n\nclass OnboardingStepImpl implements OnboardingStep {\n id: string;\n label: string;\n instructions: string;\n status: OnboardingStepStatus;\n error?: string;\n nextSibling?: OnboardingStepImpl;\n\n private readonly _action: StepAction;\n private readonly _dependencies: OnboardingStepImpl[];\n\n constructor(\n id: string,\n label: string,\n instructions: string,\n action: StepAction,\n dependencies: OnboardingStepImpl[] = [],\n initialStatus: OnboardingStepStatus = \"pending\",\n ) {\n this.id = id;\n this.label = label;\n this.instructions = instructions;\n this._action = action;\n this._dependencies = dependencies;\n this.status = initialStatus;\n }\n\n async complete(...args: unknown[]): Promise<StepResult> {\n // Check dependencies\n for (const dep of this._dependencies) {\n if (dep.status !== \"completed\") {\n const error = `Cannot complete '${this.id}': dependency '${dep.id}' is not completed.`;\n this.status = \"failed\";\n this.error = error;\n return { success: false, error };\n }\n }\n\n try {\n const result = await this._action(...args);\n if (result.success) {\n this.status = \"completed\";\n this.error = undefined;\n // Promote next step to ready\n if (this.nextSibling?.status === \"pending\") {\n this.nextSibling.status = \"ready\";\n result.nextStep = this.nextSibling;\n }\n } else {\n this.status = \"failed\";\n this.error = result.error;\n }\n return result;\n } catch (err) {\n const message = toErrorMessage(err);\n this.status = \"failed\";\n this.error = message;\n return { success: false, error: message };\n }\n }\n}\n\nexport class BotOnboarding {\n readonly id: string;\n readonly name: string;\n readonly steps: OnboardingStep[];\n\n private _bot: Bot | undefined;\n private _token: string | undefined;\n\n constructor(id: string, name: string) {\n this.id = id;\n this.name = name;\n const provideToken = new OnboardingStepImpl(\n \"provide_token\",\n \"Provide Bot Token\",\n \"Enter your Discord bot token. You can find this in the Discord Developer Portal under your application's Bot settings.\",\n async (...args: unknown[]) => {\n const token = args[0] as string;\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(token);\n await client.destroy();\n this._token = token;\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [],\n \"ready\",\n );\n\n const inviteToServer = new OnboardingStepImpl(\n \"invite_to_server\",\n \"Invite Bot to Server\",\n \"Add the bot to at least one Discord server using the OAuth2 URL from the Developer Portal.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n const client = new Client({ intents: [...DEFAULT_INTENTS], partials: [...DEFAULT_PARTIALS] });\n try {\n await client.login(this._token);\n if (client.guilds.cache.size === 0) {\n await client.destroy();\n return { success: false, error: \"Bot is not in any server yet\" };\n }\n await client.destroy();\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [provideToken],\n );\n\n const verifyPermissions = new OnboardingStepImpl(\n \"verify_permissions\",\n \"Verify Permissions\",\n \"Verify the bot has the required permissions in your server.\",\n async () => {\n if (!this._token) {\n return {\n success: false,\n error: \"Token not set — complete the provide_token step first.\",\n };\n }\n try {\n this._bot = await Bot.fromConfig({\n id: this.id,\n name: this.name,\n token: this._token,\n });\n return { success: true };\n } catch (err) {\n return { success: false, error: toErrorMessage(err) };\n }\n },\n [inviteToServer],\n );\n\n // Wire nextSibling chain for step promotion\n provideToken.nextSibling = inviteToServer;\n inviteToServer.nextSibling = verifyPermissions;\n\n this.steps = [provideToken, inviteToServer, verifyPermissions];\n }\n\n get bot(): Bot | undefined {\n return this._bot;\n }\n}\n","import { Bot } from \"./bot.js\";\nimport { BotOnboarding } from \"./onboarding.js\";\nimport type { MessageEvent, TypingEvent } from \"./types.js\";\n\ntype LifecycleEvent = \"botReady\" | \"botDisconnected\" | \"botError\";\n\nexport class ConnectorManager {\n private botMap = new Map<string, Bot>();\n private botUnsubscribers = new Map<string, (() => void)[]>();\n private messageHandlers: ((event: MessageEvent, bot: Bot) => void)[] = [];\n private typingHandlers: ((event: TypingEvent, bot: Bot) => void)[] = [];\n private lifecycleHandlers = new Map<LifecycleEvent, ((...args: unknown[]) => void)[]>();\n\n addBot(bot: Bot): void {\n if (this.botMap.has(bot.id)) {\n throw new Error(`Bot with id \"${bot.id}\" already exists in this manager.`);\n }\n this.botMap.set(bot.id, bot);\n\n const unsubs: (() => void)[] = [];\n unsubs.push(\n bot.onMessage((event) => {\n for (const handler of this.messageHandlers) {\n handler(event, bot);\n }\n }),\n );\n unsubs.push(\n bot.on(\"error\", (error) => {\n this.emitLifecycle(\"botError\", bot, error);\n }),\n );\n if (this.typingHandlers.length > 0) {\n this.subscribeTyping(bot, unsubs);\n }\n this.botUnsubscribers.set(bot.id, unsubs);\n }\n\n getBot(id: string): Bot | undefined {\n return this.botMap.get(id);\n }\n\n get bots(): Iterable<Bot> {\n return this.botMap.values();\n }\n\n removeBot(id: string): void {\n const unsubs = this.botUnsubscribers.get(id);\n if (unsubs) {\n for (const unsub of unsubs) unsub();\n this.botUnsubscribers.delete(id);\n }\n this.botMap.delete(id);\n }\n\n status(): { id: string; name: string; status: string }[] {\n return [...this.botMap.values()].map((bot) => ({\n id: bot.id,\n name: bot.name,\n status: bot.status,\n }));\n }\n\n onMessage(handler: (event: MessageEvent, bot: Bot) => void): void {\n this.messageHandlers.push(handler);\n }\n\n onTypingStart(handler: (event: TypingEvent, bot: Bot) => void): void {\n const isFirst = this.typingHandlers.length === 0;\n this.typingHandlers.push(handler);\n if (isFirst) {\n for (const [id, bot] of this.botMap) {\n const unsubs = this.botUnsubscribers.get(id) ?? [];\n this.subscribeTyping(bot, unsubs);\n }\n }\n }\n\n on(event: LifecycleEvent, callback: (...args: unknown[]) => void): void {\n const handlers = this.lifecycleHandlers.get(event) ?? [];\n handlers.push(callback);\n this.lifecycleHandlers.set(event, handlers);\n }\n\n private subscribeTyping(bot: Bot, unsubs: (() => void)[]): void {\n try {\n unsubs.push(\n bot.onTypingStart((event) => {\n for (const handler of this.typingHandlers) {\n handler(event, bot);\n }\n }),\n );\n } catch (error) {\n // Re-throw unexpected errors; only suppress the known intent-check error\n const isIntentError = error instanceof Error && error.message.startsWith(\"onTypingStart requires\");\n if (!isIntentError) throw error;\n }\n }\n\n private emitLifecycle(event: LifecycleEvent, ...args: unknown[]): void {\n const handlers = this.lifecycleHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n handler(...args);\n }\n }\n }\n\n onboardBot(id: string, name: string): BotOnboarding {\n return new BotOnboarding(id, name);\n }\n\n async disconnectAll(): Promise<void> {\n await Promise.all([...this.botMap.values()].map((bot) => bot.disconnect()));\n }\n\n async shutdown(): Promise<void> {\n await this.disconnectAll();\n }\n}\n"],"mappings":"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","names":["chunks","text","Client","Client"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scotthamilton77/discord-bot-lib",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "TypeScript library for managing Discord bot identities, messaging, and multi-bot coordination",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,7 +24,7 @@
24
24
  "lint:fix": "eslint . --fix",
25
25
  "format": "prettier --write .",
26
26
  "format:check": "prettier --check .",
27
- "harness": "npx tsx examples/test-harness.ts",
27
+ "harness": "npx tsx --env-file=.env examples/test-harness.ts",
28
28
  "prepublishOnly": "npm run build && npm run typecheck && npm run test"
29
29
  },
30
30
  "keywords": [